summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadNotificationManager.java1
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java32
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/KeyStoreHelper.java78
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java20
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java1
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java50
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java10
-rw-r--r--app/src/main/res/values/strings.xml1
-rw-r--r--app/src/normal/assets/riseup.net.pem12
-rw-r--r--app/src/normalProductionFatDebug/assets/calyx.net.json37
-rw-r--r--app/src/normalProductionFatDebug/assets/calyx.net.pem31
-rw-r--r--app/src/normalProductionFatDebug/assets/float.hexacab.org.json37
-rw-r--r--app/src/normalProductionFatDebug/assets/float.hexacab.org.pem42
-rw-r--r--app/src/normalProductionFatDebug/assets/riseup.net.json37
-rw-r--r--app/src/normalProductionFatDebug/assets/riseup.net.pem42
-rw-r--r--app/src/normalProductionFatDebug/assets/urls/calyx.net.url5
-rw-r--r--app/src/normalProductionFatDebug/assets/urls/float.hexacab.org.url6
-rw-r--r--app/src/normalProductionFatDebug/assets/urls/riseup.net.url6
-rw-r--r--app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java6
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java69
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/eip/VpnCertificateValidatorTest.java69
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java14
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/testutils/TestCalendarProvider.java (renamed from app/src/androidTest/legacy/TestCalendarProvider.java)4
-rw-r--r--app/src/test/resources/error_messages.json1
-rw-r--r--app/src/test/resources/float.hexacab.org.pem42
25 files changed, 500 insertions, 153 deletions
diff --git a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadNotificationManager.java b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadNotificationManager.java
index aaf487aa..939ed852 100644
--- a/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadNotificationManager.java
+++ b/app/src/fatweb/java/se.leap.bitmaskclient/appUpdate/DownloadNotificationManager.java
@@ -111,6 +111,7 @@ public class DownloadNotificationManager {
NotificationManager.IMPORTANCE_LOW);
channel.setSound(null, null);
channel.setDescription(description);
+ channel.setLightColor(Color.BLUE);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
notificationManager.createNotificationChannel(channel);
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 6c242e5a..27943022 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
@@ -37,6 +37,7 @@ import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
+import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
@@ -44,7 +45,9 @@ 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.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -99,25 +102,28 @@ public class ConfigHelper {
return ret;
}
- public static X509Certificate parseX509CertificateFromString(String certificateString) {
- java.security.cert.Certificate certificate = null;
+ public static ArrayList<X509Certificate> parseX509CertificatesFromString(String certificateString) {
+ Collection<? extends Certificate> certificates;
CertificateFactory cf;
try {
cf = CertificateFactory.getInstance("X.509");
- certificateString = certificateString.replaceFirst("-----BEGIN CERTIFICATE-----", "").replaceFirst("-----END CERTIFICATE-----", "").trim();
- byte[] cert_bytes = Base64.decode(certificateString);
- InputStream caInput = new ByteArrayInputStream(cert_bytes);
- try {
- certificate = cf.generateCertificate(caInput);
- System.out.println("ca=" + ((X509Certificate) certificate).getSubjectDN());
- } finally {
- caInput.close();
+ certificateString = certificateString.replaceAll("-----BEGIN CERTIFICATE-----", "").trim().replaceAll("-----END CERTIFICATE-----", "").trim();
+ byte[] certBytes = Base64.decode(certificateString);
+ try (InputStream caInput = new ByteArrayInputStream(certBytes)) {
+ certificates = cf.generateCertificates(caInput);
+ if (certificates != null) {
+ for (Certificate cert : certificates) {
+ System.out.println("ca=" + ((X509Certificate) cert).getSubjectDN());
+ }
+ return (ArrayList<X509Certificate>) certificates;
+ }
}
- } catch (NullPointerException | CertificateException | IOException | IllegalArgumentException e) {
- return null;
+ } catch (NullPointerException | CertificateException | IOException | IllegalArgumentException | ClassCastException e) {
+ e.printStackTrace();
}
- return (X509Certificate) certificate;
+
+ return null;
}
public static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/KeyStoreHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/KeyStoreHelper.java
deleted file mode 100644
index b0b28993..00000000
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/KeyStoreHelper.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package se.leap.bitmaskclient.base.utils;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-
-/**
- * Created by cyberta on 18.03.18.
- */
-
-public class KeyStoreHelper {
- private static KeyStore trustedKeystore;
-
- /**
- * Adds a new X509 certificate given its input stream and its provider name
- *
- * @param provider used to store the certificate in the keystore
- * @param inputStream from which X509 certificate must be generated.
- */
- public static void addTrustedCertificate(String provider, InputStream inputStream) {
- CertificateFactory cf;
- try {
- cf = CertificateFactory.getInstance("X.509");
- X509Certificate cert =
- (X509Certificate) cf.generateCertificate(inputStream);
- trustedKeystore.setCertificateEntry(provider, cert);
- } catch (CertificateException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (KeyStoreException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
-
- /**
- * Adds a new X509 certificate given in its string from and using its provider name
- *
- * @param provider used to store the certificate in the keystore
- * @param certificate
- */
- public static void addTrustedCertificate(String provider, String certificate) {
-
- try {
- X509Certificate cert = ConfigHelper.parseX509CertificateFromString(certificate);
- if (trustedKeystore == null) {
- trustedKeystore = KeyStore.getInstance("BKS");
- trustedKeystore.load(null);
- }
- trustedKeystore.setCertificateEntry(provider, cert);
- } catch (KeyStoreException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (NoSuchAlgorithmException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (CertificateException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
-
- /**
- * @return class wide keystore
- */
- public static KeyStore getKeystore() {
- return trustedKeystore;
- }
-
-}
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java
index c747b731..16d1c5ad 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java
@@ -16,9 +16,12 @@
*/
package se.leap.bitmaskclient.eip;
+import androidx.annotation.VisibleForTesting;
+
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@@ -27,7 +30,7 @@ import se.leap.bitmaskclient.base.utils.ConfigHelper;
public class VpnCertificateValidator {
public final static String TAG = VpnCertificateValidator.class.getSimpleName();
- private String certificate;
+ private final String certificate;
private CalendarProviderInterface calendarProvider;
public VpnCertificateValidator(String certificate) {
@@ -35,21 +38,30 @@ public class VpnCertificateValidator {
this.calendarProvider = new CalendarProvider();
}
+ @VisibleForTesting
public void setCalendarProvider(CalendarProviderInterface calendarProvider) {
this.calendarProvider = calendarProvider;
}
/**
*
- * @return true if there's a certificate that is valid for more than 15 more days
+ * @return true if all certificates are valid for more than 15 more days
*/
public boolean isValid() {
if (certificate.isEmpty()) {
return false;
}
- X509Certificate x509Certificate = ConfigHelper.parseX509CertificateFromString(certificate);
- return isValid(x509Certificate);
+ ArrayList<X509Certificate> x509Certificates = ConfigHelper.parseX509CertificatesFromString(certificate);
+ if (x509Certificates == null) {
+ return false;
+ }
+ for (X509Certificate cert : x509Certificates) {
+ if (!isValid(cert)) {
+ return false;
+ }
+ }
+ return true;
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java
index 667b8892..d2603533 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java
@@ -238,7 +238,6 @@ public class VpnNotificationManager {
channel.setDescription(context.getString(R.string.channel_description_status));
channel.enableLights(true);
- channel.setLightColor(Color.BLUE);
channel.setSound(null, null);
compatNotificationManager.createNotificationChannel(channel);
}
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 808d9e75..63cf03cf 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java
@@ -849,18 +849,24 @@ public abstract class ProviderApiManagerBase {
protected boolean validCertificate(Provider provider, String certString) {
boolean result = false;
if (!ConfigHelper.checkErroneousDownload(certString)) {
- X509Certificate certificate = ConfigHelper.parseX509CertificateFromString(certString);
+ ArrayList<X509Certificate> certificates = ConfigHelper.parseX509CertificatesFromString(certString);
try {
- if (certificate != null) {
- JSONObject providerJson = provider.getDefinition();
- String fingerprint = providerJson.getString(Provider.CA_CERT_FINGERPRINT);
- String encoding = fingerprint.split(":")[0];
- String expectedFingerprint = fingerprint.split(":")[1];
- String realFingerprint = getFingerprintFromCertificate(certificate, encoding);
-
- result = realFingerprint.trim().equalsIgnoreCase(expectedFingerprint.trim());
- } else
+ if (certificates != null) {
+ if (certificates.size() == 1) {
+ JSONObject providerJson = provider.getDefinition();
+ String fingerprint = providerJson.getString(Provider.CA_CERT_FINGERPRINT);
+ String encoding = fingerprint.split(":")[0];
+ String expectedFingerprint = fingerprint.split(":")[1];
+ String realFingerprint = getFingerprintFromCertificate(certificates.get(0), encoding);
+ result = realFingerprint.trim().equalsIgnoreCase(expectedFingerprint.trim());
+ } else {
+ // otherwise we assume the provider is transitioning the CA certs and thus shipping multiple CA certs
+ // in that case we don't do cert pinning
+ result = true;
+ }
+ } else {
result = false;
+ }
} catch (JSONException | NoSuchAlgorithmException | CertificateEncodingException e) {
result = false;
}
@@ -910,18 +916,24 @@ public abstract class ProviderApiManagerBase {
return result;
}
- X509Certificate certificate = ConfigHelper.parseX509CertificateFromString(caCert);
- if (certificate == null) {
+ ArrayList<X509Certificate> certificates = ConfigHelper.parseX509CertificatesFromString(caCert);
+ if (certificates == null) {
return setErrorResult(result, warning_corrupted_provider_cert, ERROR_INVALID_CERTIFICATE.toString());
}
try {
- certificate.checkValidity();
String encoding = provider.getCertificatePinEncoding();
String expectedFingerprint = provider.getCertificatePin();
- String realFingerprint = getFingerprintFromCertificate(certificate, encoding);
- if (!realFingerprint.trim().equalsIgnoreCase(expectedFingerprint.trim())) {
- return setErrorResult(result, warning_corrupted_provider_cert, ERROR_CERTIFICATE_PINNING.toString());
+ // Do certificate pinning only if we have 1 cert, otherwise we assume some transitioning of
+ // X509 certs, therefore we cannot do cert pinning
+ if (certificates.size() == 1) {
+ String realFingerprint = getFingerprintFromCertificate(certificates.get(0), encoding);
+ if (!realFingerprint.trim().equalsIgnoreCase(expectedFingerprint.trim())) {
+ return setErrorResult(result, warning_corrupted_provider_cert, ERROR_CERTIFICATE_PINNING.toString());
+ }
+ }
+ for (X509Certificate certificate : certificates) {
+ certificate.checkValidity();
}
if (!canConnect(provider, result)) {
@@ -1073,9 +1085,9 @@ public abstract class ProviderApiManagerBase {
keyString = Base64.encodeToString(key.getEncoded(), Base64.DEFAULT);
provider.setPrivateKey( "-----BEGIN RSA PRIVATE KEY-----\n" + keyString + "-----END RSA PRIVATE KEY-----");
- X509Certificate certificate = ConfigHelper.parseX509CertificateFromString(certificateString);
- certificate.checkValidity();
- certificateString = Base64.encodeToString(certificate.getEncoded(), Base64.DEFAULT);
+ ArrayList<X509Certificate> certificates = ConfigHelper.parseX509CertificatesFromString(certificateString);
+ certificates.get(0).checkValidity();
+ certificateString = Base64.encodeToString(certificates.get(0).getEncoded(), Base64.DEFAULT);
provider.setVpnCertificate( "-----BEGIN CERTIFICATE-----\n" + certificateString + "-----END CERTIFICATE-----");
result.putBoolean(BROADCAST_RESULT_KEY, true);
} catch (CertificateException | NullPointerException e) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java
index 5357fd74..cc68b5a8 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java
@@ -12,6 +12,8 @@ import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Arrays;
import javax.net.ssl.SSLContext;
@@ -55,8 +57,12 @@ public class TLSCompatSocketFactory extends SSLSocketFactory {
KeyStore keyStore = KeyStore.getInstance(defaultType);
keyStore.load(null, null);
if (!TextUtils.isEmpty(trustedSelfSignedCaCert)) {
- java.security.cert.Certificate provider_certificate = ConfigHelper.parseX509CertificateFromString(trustedSelfSignedCaCert);
- keyStore.setCertificateEntry("provider_ca_certificate", provider_certificate);
+ ArrayList<X509Certificate> x509Certificates = ConfigHelper.parseX509CertificatesFromString(trustedSelfSignedCaCert);
+ if (x509Certificates != null) {
+ for (int i = 0; i < x509Certificates.size(); i++) {
+ keyStore.setCertificateEntry("provider_ca_certificate"+i, x509Certificates.get(i));
+ }
+ }
}
// Create a TrustManager that trusts the CAs in our KeyStore
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6251949a..d1f03288 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -52,6 +52,7 @@
<string name="setup_error_configure_button">Configure</string>
<string name="setup_error_close_button">Exit</string>
<string name="setup_error_text">There was an error configuring %s with your chosen provider.\n\nYou may choose to reconfigure, or exit and configure a provider upon next launch.</string>
+ <string name="setup_error_text_custom">There was an error configuring %s.\n\nYou may choose to reconfigure, or exit.</string>
<string name="server_unreachable_message">The server is unreachable, please try again.</string>
<string name="error.security.pinnedcertificate">Security error, upgrade the app or choose another provider.</string>
<string name="malformed_url">It doesn\'t seem to be a %s provider.</string>
diff --git a/app/src/normal/assets/riseup.net.pem b/app/src/normal/assets/riseup.net.pem
index c890aff4..8c7ad4e4 100644
--- a/app/src/normal/assets/riseup.net.pem
+++ b/app/src/normal/assets/riseup.net.pem
@@ -29,4 +29,14 @@ tsK3pwE0FEqGnT4qMFbixQmc8bGyuakr23wjMvfO7eZUxBuWYR2SkcP26sozF9PF
tGhbZHQVGZUTVPyvwahMUEhbPGVerOW0IYpxkm0x/eaWdTc4vPpf/rIlgbAjarnJ
UN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp
0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO
------END CERTIFICATE----- \ No newline at end of file
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBYjCCAQigAwIBAgIBATAKBggqhkjOPQQDAjAXMRUwEwYDVQQDEwxMRUFQIFJv
+b3QgQ0EwHhcNMjExMTAyMTkwNTM3WhcNMjYxMTAyMTkxMDM3WjAXMRUwEwYDVQQD
+EwxMRUFQIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQxOXBGu+gf
+pjHzVteGTWL6XnFxtEnKMFpKaJkA/VOHmESzoLsZRQxt88GssxaqC01J17idQiqv
+zgNpedmtvFtyo0UwQzAOBgNVHQ8BAf8EBAMCAqQwEgYDVR0TAQH/BAgwBgEB/wIB
+ATAdBgNVHQ4EFgQUZdoUlJrCIUNFrpffAq+LQjnwEz4wCgYIKoZIzj0EAwIDSAAw
+RQIgfr3w4tnRG+NdI3LsGPlsRktGK20xHTzsB3orB0yC6cICIQCB+/9y8nmSStfN
+VUMUyk2hNd7/kC8nL222TTD7VZUtsg==
+-----END CERTIFICATE-----
diff --git a/app/src/normalProductionFatDebug/assets/calyx.net.json b/app/src/normalProductionFatDebug/assets/calyx.net.json
new file mode 100644
index 00000000..1e3a9e7b
--- /dev/null
+++ b/app/src/normalProductionFatDebug/assets/calyx.net.json
@@ -0,0 +1,37 @@
+{
+ "api_uri": "https://api.calyx.net:4430",
+ "api_version": "1",
+ "ca_cert_fingerprint": "SHA256: 43683c9ba3862c5384a8c1885072fcac40b5d2d4dd67331443f13a3077fa2e69",
+ "ca_cert_uri": "https://calyx.net/ca.crt",
+ "default_language": "en",
+ "description": {
+ "en": "Calyx Institute privacy focused ISP testbed"
+ },
+ "domain": "calyx.net",
+ "enrollment_policy": "open",
+ "languages": [
+ "en"
+ ],
+ "name": {
+ "en": "calyx"
+ },
+ "service": {
+ "allow_anonymous": true,
+ "allow_free": true,
+ "allow_limited_bandwidth": false,
+ "allow_paid": false,
+ "allow_registration": true,
+ "allow_unlimited_bandwidth": true,
+ "bandwidth_limit": 102400,
+ "default_service_level": 1,
+ "levels": {
+ "1": {
+ "description": "Please donate.",
+ "name": "free"
+ }
+ }
+ },
+ "services": [
+ "openvpn"
+ ]
+} \ No newline at end of file
diff --git a/app/src/normalProductionFatDebug/assets/calyx.net.pem b/app/src/normalProductionFatDebug/assets/calyx.net.pem
new file mode 100644
index 00000000..cedb2e38
--- /dev/null
+++ b/app/src/normalProductionFatDebug/assets/calyx.net.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQ0FADBEMQ4wDAYDVQQKDAVjYWx5
+eDEaMBgGA1UECwwRaHR0cHM6Ly9jYWx5eC5uZXQxFjAUBgNVBAMMDWNhbHl4IFJv
+b3QgQ0EwHhcNMTMwNzAyMDAwMDAwWhcNMjMwNzAyMDAwMDAwWjBEMQ4wDAYDVQQK
+DAVjYWx5eDEaMBgGA1UECwwRaHR0cHM6Ly9jYWx5eC5uZXQxFjAUBgNVBAMMDWNh
+bHl4IFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDupdnx
+Bgat537XOqrZOulE/RvjoXB1S07sy9/MMtksXFoQuWJZRCSTp1Jaqg3H/e9o1nct
+LQO91+izfJe07TUyajFl7CfllYgMeyKTYcT85dFwNX4pcIHZr8UpmO0MpGBoR4W1
+8cPa3vxAG0CsyUmrASJVyhRouk4qazRosM5RwBxTdMzCK7L3SwqPQoxlY9YmRJlD
+XYZlK5VMJd0dj9XxhMeFs5n43R0bsDENryrExSbuxoNfnUoQg3wffKk+Z0gW7YgW
+ivPsbObqOgXUuBEU0xr9xMNBpU33ffLIsccrHq1EKp8zGfCOcww6v7+zEadUkVLo
+6j/rRhYYgRw9lijZG1rMuV/mTGnUqbjHsdoz5mzkFFWeTSqo44lvhveUyCcwRNmi
+2sjS77l0fCTzfreufffFoOEcRVMRfsnJdu/xPeARoXILEx8nQ421mSn6spOZlDQr
+Tt0T0BAWt+VNc+m0IGSW3SwS7r5MUyQ/M5GrbQBGi5W2SzPriKZ79YTOwPVmXKLZ
+vJoEuKRDkEPJLBAhcD5oSQljOm/Wp/hjmRH4HnI1y4XMshWlDsyRDB1Au5yrsfwN
+noFVSskEcbXlZfNgml4lktLBqz+qwsw+voq6Ak7ROKbc0ii5s8+iNMbAtIK7GcFF
+kuKKIyRmmGlDim/SDhlNdWo7Ah4Akde7zfWufwIDAQABo2AwXjAdBgNVHQ4EFgQU
+AY8+K4ZupAQ+L9ttFJG3vaLBq5gwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMB
+Af8wHwYDVR0jBBgwFoAUAY8+K4ZupAQ+L9ttFJG3vaLBq5gwDQYJKoZIhvcNAQEN
+BQADggIBAOpXi5o3g/2o2rPa53iG7Zgcy8RpePGgZk6xknGYWeLamEqSh+XWQZ2w
+2kQP54bf8HfPj3ugJBWsVtYAs/ltJwzeBfYDrwEJd1N8tw2IRuGlQOWiTAVVLBj4
+Zs+dikSuMoA399f/7BlUIEpVLUiV/emTtbkjFnDeKEV9zql6ypR0BtR8Knf8ALvL
+YfMsWLvTe4rXeypzxIaE2pn8ttcXLYAX0ml2MofTi5xcDhMn1vznKIvs82xhncQx
+I1MJMWqPHNHgJUJpA+y1IFh5LPbpag9PKQ0yQ9sM+/dyGumF2jElsMw71flh/Txr
+2dEv8+FNV1pPK26XJZBK24rNWFs30eAFfH9EQCwVla174I4PDoWqsIR7vtQMObDt
+Bq34R3TjjJJIt2sCSlYLooWwiK7Q+d/SgYqA+MSDmmwhzm86ToK6cwbCsvuw1AxR
+X6VIs4U8wOotgljzX/CSpKqlxcqZjhnAuelZ1+KiN8RHKPj7AzSLYOv/YwTjLTIq
+EOxquoNR58uDa5pBG22a7xWbSaKosn/mEl8SrUr6klzzc8Vh09IMoxrw74uLdAg2
+1jnrhm7qg91Ttb0aXiqbV+Kg/qQzojdewnnoBFnv4jaQ3y8zDCfMhsBtWlWz4Knb
+Zqga1WyRm3Gj1j6IV0oOincYMrw5YA7bgXpwop/Lo/mmliMA14ps
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/app/src/normalProductionFatDebug/assets/float.hexacab.org.json b/app/src/normalProductionFatDebug/assets/float.hexacab.org.json
new file mode 100644
index 00000000..d5ba9819
--- /dev/null
+++ b/app/src/normalProductionFatDebug/assets/float.hexacab.org.json
@@ -0,0 +1,37 @@
+{
+ "api_uri":"https://api.float.hexacab.org:4430",
+ "api_version":"3",
+ "ca_cert_fingerprint":"SHA256: dd919b7513b4a1368faa20e38cd3314156805677f48b787cdd9b4a92dec64eb0",
+ "ca_cert_uri":"https://api.float.hexacab.org/ca.crt",
+ "default_language":"en",
+ "description":{
+ "en":"Riseup Networks"
+ },
+ "domain":"float.hexacab.org",
+ "enrollment_policy":"open",
+ "languages":[
+ "en"
+ ],
+ "name":{
+ "en":"0XACAB Networks"
+ },
+ "service":{
+ "allow_anonymous":true,
+ "allow_free":true,
+ "allow_limited_bandwidth":false,
+ "allow_paid":false,
+ "allow_registration":false,
+ "allow_unlimited_bandwidth":true,
+ "bandwidth_limit":102400,
+ "default_service_level":1,
+ "levels":{
+ "1":{
+ "description":"Please donate.",
+ "name":"free"
+ }
+ }
+ },
+ "services":[
+ "openvpn"
+ ]
+} \ No newline at end of file
diff --git a/app/src/normalProductionFatDebug/assets/float.hexacab.org.pem b/app/src/normalProductionFatDebug/assets/float.hexacab.org.pem
new file mode 100644
index 00000000..96306a8c
--- /dev/null
+++ b/app/src/normalProductionFatDebug/assets/float.hexacab.org.pem
@@ -0,0 +1,42 @@
+-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBZMRgwFgYDVQQKDA9SaXNl
+dXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UE
+AwwXUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EwHhcNMTQwNDI4MDAwMDAwWhcNMjQw
+NDI4MDAwMDAwWjBZMRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsM
+Emh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UEAwwXUmlzZXVwIE5ldHdvcmtzIFJv
+b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC76J4ciMJ8Sg0m
+TP7DF2DT9zNe0Csk4myoMFC57rfJeqsAlJCv1XMzBmXrw8wq/9z7XHv6n/0sWU7a
+7cF2hLR33ktjwODlx7vorU39/lXLndo492ZBhXQtG1INMShyv+nlmzO6GT7ESfNE
+LliFitEzwIegpMqxCIHXFuobGSCWF4N0qLHkq/SYUMoOJ96O3hmPSl1kFDRMtWXY
+iw1SEKjUvpyDJpVs3NGxeLCaA7bAWhDY5s5Yb2fA1o8ICAqhowurowJpW7n5ZuLK
+5VNTlNy6nZpkjt1QycYvNycffyPOFm/Q/RKDlvnorJIrihPkyniV3YY5cGgP+Qkx
+HUOT0uLA6LHtzfiyaOqkXwc4b0ZcQD5Vbf6Prd20Ppt6ei0zazkUPwxld3hgyw58
+m/4UIjG3PInWTNf293GngK2Bnz8Qx9e/6TueMSAn/3JBLem56E0WtmbLVjvko+LF
+PM5xA+m0BmuSJtrD1MUCXMhqYTtiOvgLBlUm5zkNxALzG+cXB28k6XikXt6MRG7q
+hzIPG38zwkooM55yy5i1YfcIi5NjMH6A+t4IJxxwb67MSb6UFOwg5kFokdONZcwj
+shczHdG9gLKSBIvrKa03Nd3W2dF9hMbRu//STcQxOailDBQCnXXfAATj9pYzdY4k
+ha8VCAREGAKTDAex9oXf1yRuktES4QIDAQABo2AwXjAdBgNVHQ4EFgQUC4tdmLVu
+f9hwfK4AGliaet5KkcgwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMBAf8wHwYD
+VR0jBBgwFoAUC4tdmLVuf9hwfK4AGliaet5KkcgwDQYJKoZIhvcNAQENBQADggIB
+AGzL+GRnYu99zFoy0bXJKOGCF5XUXP/3gIXPRDqQf5g7Cu/jYMID9dB3No4Zmf7v
+qHjiSXiS8jx1j/6/Luk6PpFbT7QYm4QLs1f4BlfZOti2KE8r7KRDPIecUsUXW6P/
+3GJAVYH/+7OjA39za9AieM7+H5BELGccGrM5wfl7JeEz8in+V2ZWDzHQO4hMkiTQ
+4ZckuaL201F68YpiItBNnJ9N5nHr1MRiGyApHmLXY/wvlrOpclh95qn+lG6/2jk7
+3AmihLOKYMlPwPakJg4PYczm3icFLgTpjV5sq2md9bRyAg3oPGfAuWHmKj2Ikqch
+Td5CHKGxEEWbGUWEMP0s1A/JHWiCbDigc4Cfxhy56CWG4q0tYtnc2GMw8OAUO6Wf
+Xu5pYKNkzKSEtT/MrNJt44tTZWbKV/Pi/N2Fx36my7TgTUj7g3xcE9eF4JV2H/sg
+tsK3pwE0FEqGnT4qMFbixQmc8bGyuakr23wjMvfO7eZUxBuWYR2SkcP26sozF9PF
+tGhbZHQVGZUTVPyvwahMUEhbPGVerOW0IYpxkm0x/eaWdTc4vPpf/rIlgbAjarnJ
+UN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp
+0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBYjCCAQigAwIBAgIBATAKBggqhkjOPQQDAjAXMRUwEwYDVQQDEwxMRUFQIFJv
+b3QgQ0EwHhcNMjExMTAyMTkwNTM3WhcNMjYxMTAyMTkxMDM3WjAXMRUwEwYDVQQD
+EwxMRUFQIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQxOXBGu+gf
+pjHzVteGTWL6XnFxtEnKMFpKaJkA/VOHmESzoLsZRQxt88GssxaqC01J17idQiqv
+zgNpedmtvFtyo0UwQzAOBgNVHQ8BAf8EBAMCAqQwEgYDVR0TAQH/BAgwBgEB/wIB
+ATAdBgNVHQ4EFgQUZdoUlJrCIUNFrpffAq+LQjnwEz4wCgYIKoZIzj0EAwIDSAAw
+RQIgfr3w4tnRG+NdI3LsGPlsRktGK20xHTzsB3orB0yC6cICIQCB+/9y8nmSStfN
+VUMUyk2hNd7/kC8nL222TTD7VZUtsg==
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/app/src/normalProductionFatDebug/assets/riseup.net.json b/app/src/normalProductionFatDebug/assets/riseup.net.json
new file mode 100644
index 00000000..7c5bc6d0
--- /dev/null
+++ b/app/src/normalProductionFatDebug/assets/riseup.net.json
@@ -0,0 +1,37 @@
+{
+ "api_uri": "https://api.black.riseup.net:443",
+ "api_version": "3",
+ "ca_cert_fingerprint": "SHA256: a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494",
+ "ca_cert_uri": "https://black.riseup.net/ca.crt",
+ "default_language": "en",
+ "description": {
+ "en": "Riseup is a non-profit collective in Seattle that provides online communication tools for people and groups working toward liberatory social change."
+ },
+ "domain": "riseup.net",
+ "enrollment_policy": "open",
+ "languages": [
+ "en"
+ ],
+ "name": {
+ "en": "Riseup Networks"
+ },
+ "service": {
+ "allow_anonymous": true,
+ "allow_free": true,
+ "allow_limited_bandwidth": false,
+ "allow_paid": false,
+ "allow_registration": false,
+ "allow_unlimited_bandwidth": true,
+ "bandwidth_limit": 102400,
+ "default_service_level": 1,
+ "levels": {
+ "1": {
+ "description": "Please donate.",
+ "name": "free"
+ }
+ }
+ },
+ "services": [
+ "openvpn"
+ ]
+} \ No newline at end of file
diff --git a/app/src/normalProductionFatDebug/assets/riseup.net.pem b/app/src/normalProductionFatDebug/assets/riseup.net.pem
new file mode 100644
index 00000000..8c7ad4e4
--- /dev/null
+++ b/app/src/normalProductionFatDebug/assets/riseup.net.pem
@@ -0,0 +1,42 @@
+-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBZMRgwFgYDVQQKDA9SaXNl
+dXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UE
+AwwXUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EwHhcNMTQwNDI4MDAwMDAwWhcNMjQw
+NDI4MDAwMDAwWjBZMRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsM
+Emh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UEAwwXUmlzZXVwIE5ldHdvcmtzIFJv
+b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC76J4ciMJ8Sg0m
+TP7DF2DT9zNe0Csk4myoMFC57rfJeqsAlJCv1XMzBmXrw8wq/9z7XHv6n/0sWU7a
+7cF2hLR33ktjwODlx7vorU39/lXLndo492ZBhXQtG1INMShyv+nlmzO6GT7ESfNE
+LliFitEzwIegpMqxCIHXFuobGSCWF4N0qLHkq/SYUMoOJ96O3hmPSl1kFDRMtWXY
+iw1SEKjUvpyDJpVs3NGxeLCaA7bAWhDY5s5Yb2fA1o8ICAqhowurowJpW7n5ZuLK
+5VNTlNy6nZpkjt1QycYvNycffyPOFm/Q/RKDlvnorJIrihPkyniV3YY5cGgP+Qkx
+HUOT0uLA6LHtzfiyaOqkXwc4b0ZcQD5Vbf6Prd20Ppt6ei0zazkUPwxld3hgyw58
+m/4UIjG3PInWTNf293GngK2Bnz8Qx9e/6TueMSAn/3JBLem56E0WtmbLVjvko+LF
+PM5xA+m0BmuSJtrD1MUCXMhqYTtiOvgLBlUm5zkNxALzG+cXB28k6XikXt6MRG7q
+hzIPG38zwkooM55yy5i1YfcIi5NjMH6A+t4IJxxwb67MSb6UFOwg5kFokdONZcwj
+shczHdG9gLKSBIvrKa03Nd3W2dF9hMbRu//STcQxOailDBQCnXXfAATj9pYzdY4k
+ha8VCAREGAKTDAex9oXf1yRuktES4QIDAQABo2AwXjAdBgNVHQ4EFgQUC4tdmLVu
+f9hwfK4AGliaet5KkcgwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMBAf8wHwYD
+VR0jBBgwFoAUC4tdmLVuf9hwfK4AGliaet5KkcgwDQYJKoZIhvcNAQENBQADggIB
+AGzL+GRnYu99zFoy0bXJKOGCF5XUXP/3gIXPRDqQf5g7Cu/jYMID9dB3No4Zmf7v
+qHjiSXiS8jx1j/6/Luk6PpFbT7QYm4QLs1f4BlfZOti2KE8r7KRDPIecUsUXW6P/
+3GJAVYH/+7OjA39za9AieM7+H5BELGccGrM5wfl7JeEz8in+V2ZWDzHQO4hMkiTQ
+4ZckuaL201F68YpiItBNnJ9N5nHr1MRiGyApHmLXY/wvlrOpclh95qn+lG6/2jk7
+3AmihLOKYMlPwPakJg4PYczm3icFLgTpjV5sq2md9bRyAg3oPGfAuWHmKj2Ikqch
+Td5CHKGxEEWbGUWEMP0s1A/JHWiCbDigc4Cfxhy56CWG4q0tYtnc2GMw8OAUO6Wf
+Xu5pYKNkzKSEtT/MrNJt44tTZWbKV/Pi/N2Fx36my7TgTUj7g3xcE9eF4JV2H/sg
+tsK3pwE0FEqGnT4qMFbixQmc8bGyuakr23wjMvfO7eZUxBuWYR2SkcP26sozF9PF
+tGhbZHQVGZUTVPyvwahMUEhbPGVerOW0IYpxkm0x/eaWdTc4vPpf/rIlgbAjarnJ
+UN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp
+0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBYjCCAQigAwIBAgIBATAKBggqhkjOPQQDAjAXMRUwEwYDVQQDEwxMRUFQIFJv
+b3QgQ0EwHhcNMjExMTAyMTkwNTM3WhcNMjYxMTAyMTkxMDM3WjAXMRUwEwYDVQQD
+EwxMRUFQIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQxOXBGu+gf
+pjHzVteGTWL6XnFxtEnKMFpKaJkA/VOHmESzoLsZRQxt88GssxaqC01J17idQiqv
+zgNpedmtvFtyo0UwQzAOBgNVHQ8BAf8EBAMCAqQwEgYDVR0TAQH/BAgwBgEB/wIB
+ATAdBgNVHQ4EFgQUZdoUlJrCIUNFrpffAq+LQjnwEz4wCgYIKoZIzj0EAwIDSAAw
+RQIgfr3w4tnRG+NdI3LsGPlsRktGK20xHTzsB3orB0yC6cICIQCB+/9y8nmSStfN
+VUMUyk2hNd7/kC8nL222TTD7VZUtsg==
+-----END CERTIFICATE-----
diff --git a/app/src/normalProductionFatDebug/assets/urls/calyx.net.url b/app/src/normalProductionFatDebug/assets/urls/calyx.net.url
new file mode 100644
index 00000000..0b26dc25
--- /dev/null
+++ b/app/src/normalProductionFatDebug/assets/urls/calyx.net.url
@@ -0,0 +1,5 @@
+{
+ "main_url" : "https://calyx.net",
+ "provider_ip" : "162.247.73.194",
+ "provider_api_ip" : "162.247.73.194"
+}
diff --git a/app/src/normalProductionFatDebug/assets/urls/float.hexacab.org.url b/app/src/normalProductionFatDebug/assets/urls/float.hexacab.org.url
new file mode 100644
index 00000000..675a01dd
--- /dev/null
+++ b/app/src/normalProductionFatDebug/assets/urls/float.hexacab.org.url
@@ -0,0 +1,6 @@
+{
+ "main_url" : "https://float.hexacab.org",
+ "provider_ip" : "198.252.153.67",
+ "provider_api_ip" : "198.252.153.106",
+ "geoip_url" : "https://menshen.float.hexacab.org/json"
+}
diff --git a/app/src/normalProductionFatDebug/assets/urls/riseup.net.url b/app/src/normalProductionFatDebug/assets/urls/riseup.net.url
new file mode 100644
index 00000000..3c1e6b49
--- /dev/null
+++ b/app/src/normalProductionFatDebug/assets/urls/riseup.net.url
@@ -0,0 +1,6 @@
+{
+ "main_url" : "https://riseup.net",
+ "provider_ip" : "198.252.153.70",
+ "provider_api_ip" : "198.252.153.107",
+ "geoip_url" : "https://api.black.riseup.net:9001/json"
+}
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 5416b1f8..d1de62a0 100644
--- a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
+++ b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
@@ -35,9 +35,7 @@ import okhttp3.OkHttpClient;
import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.base.models.Provider;
import se.leap.bitmaskclient.base.utils.ConfigHelper;
-import se.leap.bitmaskclient.base.utils.PreferenceHelper;
import se.leap.bitmaskclient.eip.EIP;
-import se.leap.bitmaskclient.eip.EipStatus;
import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator;
import se.leap.bitmaskclient.tor.TorStatusObservable;
@@ -47,12 +45,14 @@ import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed;
import static se.leap.bitmaskclient.R.string.error_io_exception_user_message;
import static se.leap.bitmaskclient.R.string.malformed_url;
import static se.leap.bitmaskclient.R.string.setup_error_text;
+import static se.leap.bitmaskclient.R.string.setup_error_text_custom;
import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert;
import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_details;
import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString;
+import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS;
import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING;
import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON;
@@ -121,7 +121,7 @@ public class ProviderApiManager extends ProviderApiManagerBase {
}
if (provider.hasEIP() && !provider.allowsRegistered() && !provider.allowsAnonymous()) {
- setErrorResult(currentDownload, setup_error_text, null);
+ setErrorResult(currentDownload, isDefaultBitmask() ? setup_error_text : setup_error_text_custom, null);
}
}
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 d6ee0def..4b523edb 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.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;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
@@ -78,7 +79,7 @@ import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockPr
import static se.leap.bitmaskclient.testutils.MockHelper.mockBundle;
import static se.leap.bitmaskclient.testutils.MockHelper.mockClientGenerator;
import static se.leap.bitmaskclient.testutils.MockHelper.mockConfigHelper;
-import static se.leap.bitmaskclient.testutils.MockHelper.mockFingerprintForCertificate;
+import static se.leap.bitmaskclient.testutils.MockHelper.mockConfigHelper;
import static se.leap.bitmaskclient.testutils.MockHelper.mockIntent;
import static se.leap.bitmaskclient.testutils.MockHelper.mockPreferenceHelper;
import static se.leap.bitmaskclient.testutils.MockHelper.mockProviderApiConnector;
@@ -187,7 +188,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_happyPath_preseededProviderAndCA() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
Provider provider = getConfiguredProvider();
- mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(NO_ERROR);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
Bundle expectedResult = mockBundle();
@@ -208,7 +209,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_happyPath_no_preseededProviderAndCA() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
Provider provider = getConfiguredProvider();
- mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(NO_ERROR);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
Bundle expectedResult = mockBundle();
@@ -251,7 +252,7 @@ public class ProviderApiManagerTest {
@Test
public void test_handleIntentSetupProvider_preseededProviderAndCA_failedCAPinning() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
Provider provider = getConfiguredProvider();
- mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495");
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495");
mockProviderApiConnector(NO_ERROR);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
Bundle expectedResult = mockBundle();
@@ -272,7 +273,7 @@ public class ProviderApiManagerTest {
@Test
public void test_handleIntentSetupProvider_no_preseededProviderAndCA_failedPinning() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
Provider provider = new Provider("https://riseup.net");
- mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495");
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495");
mockProviderApiConnector(NO_ERROR);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -322,6 +323,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_preseededProviderAndCA_outdatedCertificate() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
Provider provider = getProvider(null ,null, null, null, "outdated_cert.pem", null, null, null);
mockProviderApiConnector(NO_ERROR);
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
Bundle expectedResult = mockBundle();
@@ -343,6 +345,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_storedProviderAndCAFromPreviousSetup_outdatedCertificate() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
Provider provider = getConfiguredProvider(); //new Provider("https://riseup.net");
mockProviderApiConnector(NO_ERROR);
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockPreferences.edit().putString(Provider.KEY + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))).apply();
mockPreferences.edit().putString(Provider.CA_CERT + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("outdated_cert.pem"))).apply();
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -415,7 +418,7 @@ public class ProviderApiManagerTest {
Provider provider = getConfiguredProvider();
- mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(ERROR_CASE_MICONFIGURED_PROVIDER);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -435,11 +438,37 @@ public class ProviderApiManagerTest {
}
@Test
+ public void test_handleIntentSetupProvider_preseededCustomProviderAndCA_failedConfiguration() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
+
+ Provider provider = getConfiguredProvider();
+
+ mockProviderApiConnector(ERROR_CASE_MICONFIGURED_PROVIDER);
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ when(ConfigHelper.isDefaultBitmask()).thenReturn(false);
+
+ providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
+
+ Bundle expectedResult = mockBundle();
+ expectedResult.putBoolean(BROADCAST_RESULT_KEY, false);
+ expectedResult.putString(ERRORS, "{\"errors\":\"There was an error configuring RiseupVPN.\"}");
+ expectedResult.putParcelable(PROVIDER_KEY, provider);
+
+
+ Intent providerApiCommand = mockIntent();
+
+ providerApiCommand.putExtra(PROVIDER_KEY, provider);
+ providerApiCommand.setAction(ProviderAPI.SET_UP_PROVIDER);
+ providerApiCommand.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_NOK, expectedResult));
+
+ providerApiManager.handleIntent(providerApiCommand);
+ }
+
+ @Test
public void test_handleIntentSetupProvider_outdatedPreseededProviderAndCA_successfulConfiguration() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
Provider provider = getProvider(null, null, null, null, null, "riseup_net_outdated_config.json", null, null);
- mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(NO_ERROR);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -465,7 +494,7 @@ public class ProviderApiManagerTest {
Provider provider = new Provider("https://riseup.net");
- mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(ERROR_CASE_FETCH_EIP_SERVICE_CERTIFICATE_INVALID);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -493,7 +522,7 @@ public class ProviderApiManagerTest {
Provider inputProvider = getConfiguredProvider();
inputProvider.setGeoIpJson(new JSONObject());
Provider expectedProvider = getConfiguredProvider();
- mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(NO_ERROR);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -523,7 +552,7 @@ public class ProviderApiManagerTest {
}
Provider provider = getConfiguredProvider();
- mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(ERROR_GEOIP_SERVICE_IS_DOWN);
mockPreferences.edit().putBoolean(USE_BRIDGES, false).putBoolean(USE_SNOWFLAKE, false).commit();
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -554,7 +583,7 @@ public class ProviderApiManagerTest {
mockTorStatusObservable(null);
Provider provider = getConfiguredProvider();
- mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(ERROR_GEOIP_SERVICE_IS_DOWN_TOR_FALLBACK);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -586,7 +615,7 @@ public class ProviderApiManagerTest {
Provider provider = getConfiguredProvider();
provider.setLastGeoIpUpdate(System.currentTimeMillis());
- mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(NO_ERROR);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -616,7 +645,7 @@ public class ProviderApiManagerTest {
Provider provider = getConfiguredProvider();
provider.setGeoipUrl(null);
provider.setGeoIpJson(new JSONObject());
- mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(NO_ERROR);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -641,7 +670,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_APIv4_happyPath() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
Provider provider = getConfiguredProviderAPIv4();
- mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(NO_ERROR_API_V4);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
Bundle expectedResult = mockBundle();
@@ -666,7 +695,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_TorFallback_SecondTryHappyPath() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException {
Provider provider = getConfiguredProviderAPIv4();
- mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(ERROR_DNS_RESUOLUTION_TOR_FALLBACK);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -688,7 +717,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_TorFallbackStartServiceException_SecondTryFailed() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException {
Provider provider = getConfiguredProviderAPIv4();
- mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(ERROR_DNS_RESUOLUTION_TOR_FALLBACK);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback(new IllegalStateException("Tor service start not failed."), true));
@@ -707,7 +736,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_TorFallbackTimeoutException_SecondTryFailed() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException {
Provider provider = getConfiguredProviderAPIv4();
- mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(ERROR_DNS_RESUOLUTION_TOR_FALLBACK);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -726,7 +755,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_TorBridgesPreferenceEnabled_Success() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException {
Provider provider = getConfiguredProviderAPIv4();
- mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(NO_ERROR_API_V4);
mockPreferences.edit().putBoolean(USE_BRIDGES, true).putBoolean(USE_SNOWFLAKE, true).commit();
@@ -747,7 +776,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_TorBridgesDisabled_TorNotStarted() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException {
Provider provider = getConfiguredProviderAPIv4();
- mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(NO_ERROR_API_V4);
mockPreferences.edit().putBoolean(USE_BRIDGES, false).putBoolean(USE_SNOWFLAKE, false).commit();
@@ -791,7 +820,7 @@ public class ProviderApiManagerTest {
public void test_handleIntentSetupProvider_noNetwork_NetworkError() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
Provider provider = getConfiguredProvider();
- mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
+ mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
mockProviderApiConnector(NO_ERROR);
providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback(null, false));
Bundle expectedResult = mockBundle();
diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/VpnCertificateValidatorTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/VpnCertificateValidatorTest.java
new file mode 100644
index 00000000..32c0d0e4
--- /dev/null
+++ b/app/src/test/java/se/leap/bitmaskclient/eip/VpnCertificateValidatorTest.java
@@ -0,0 +1,69 @@
+package se.leap.bitmaskclient.eip;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.util.Calendar;
+
+import se.leap.bitmaskclient.base.utils.ConfigHelper;
+import se.leap.bitmaskclient.testutils.TestCalendarProvider;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static se.leap.bitmaskclient.testutils.MockHelper.mockConfigHelper;
+import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ConfigHelper.class})
+public class VpnCertificateValidatorTest {
+
+ @Before
+ public void setup() {
+ }
+
+ @Test
+ public void test_isValid() throws NoSuchAlgorithmException, CertificateEncodingException, IOException {
+ String cert = getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"));
+ Calendar c = new Calendar.Builder().setDate(2018, 1, 1).setCalendarType("gregorian").build();
+ mockConfigHelper("falseFingerPrint");
+ VpnCertificateValidator validator = new VpnCertificateValidator(cert);
+ validator.setCalendarProvider(new TestCalendarProvider(c.getTimeInMillis()));
+ assertTrue( validator.isValid());
+ }
+
+ @Test
+ public void test_isValid_lessThan15days_returnFalse() throws NoSuchAlgorithmException, CertificateEncodingException, IOException {
+ String cert = getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"));
+ Calendar c = new Calendar.Builder().setDate(2024, 4, 14).setCalendarType("gregorian").build();
+ mockConfigHelper("falseFingerPrint");
+ VpnCertificateValidator validator = new VpnCertificateValidator(cert);
+ validator.setCalendarProvider(new TestCalendarProvider(c.getTimeInMillis()));
+ assertFalse( validator.isValid());
+ }
+
+ @Test
+ public void test_isValid_multipleCerts_failIfOneExpires() throws NoSuchAlgorithmException, CertificateEncodingException, IOException {
+ String cert = getInputAsString(getClass().getClassLoader().getResourceAsStream("float.hexacab.org.pem"));
+ Calendar c = new Calendar.Builder().setDate(2024, 4, 14).setCalendarType("gregorian").build();
+ mockConfigHelper("falseFingerPrint");
+ VpnCertificateValidator validator = new VpnCertificateValidator(cert);
+ validator.setCalendarProvider(new TestCalendarProvider(c.getTimeInMillis()));
+ assertFalse(validator.isValid());
+ }
+
+ @Test
+ public void test_isValid_multipleCerts_allValid() throws NoSuchAlgorithmException, CertificateEncodingException, IOException {
+ String cert = getInputAsString(getClass().getClassLoader().getResourceAsStream("float.hexacab.org.pem"));
+ Calendar c = new Calendar.Builder().setDate(2024, 4, 13).setCalendarType("gregorian").build();
+ mockConfigHelper("falseFingerPrint");
+ VpnCertificateValidator validator = new VpnCertificateValidator(cert);
+ validator.setCalendarProvider(new TestCalendarProvider(c.getTimeInMillis()));
+ assertFalse(validator.isValid());
+ }
+} \ No newline at end of file
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 dd3053df..8d76fd41 100644
--- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
+++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
@@ -38,7 +38,6 @@ import java.util.Vector;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
-import okhttp3.Connection;
import okhttp3.OkHttpClient;
import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.base.models.Provider;
@@ -413,10 +412,11 @@ public class MockHelper {
mockStatic(ConfigHelper.class);
when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint);
when(ConfigHelper.checkErroneousDownload(anyString())).thenCallRealMethod();
- when(ConfigHelper.parseX509CertificateFromString(anyString())).thenCallRealMethod();
+ when(ConfigHelper.parseX509CertificatesFromString(anyString())).thenCallRealMethod();
when(ConfigHelper.getProviderFormattedString(any(Resources.class), anyInt())).thenCallRealMethod();
when(ConfigHelper.timezoneDistance(anyInt(), anyInt())).thenCallRealMethod();
when(ConfigHelper.isIPv4(anyString())).thenCallRealMethod();
+ when(ConfigHelper.isDefaultBitmask()).thenReturn(true);
}
public static void mockPreferenceHelper(final Provider providerFromPrefs) {
@@ -503,14 +503,6 @@ public class MockHelper {
when(ProviderObservable.getInstance()).thenAnswer((Answer<ProviderObservable>) invocation -> observable);
}
- public static void mockFingerprintForCertificate(String mockedFingerprint) throws CertificateEncodingException, NoSuchAlgorithmException {
- mockStatic(ConfigHelper.class);
- when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint);
- when(ConfigHelper.checkErroneousDownload(anyString())).thenCallRealMethod();
- when(ConfigHelper.parseX509CertificateFromString(anyString())).thenCallRealMethod();
- when(ConfigHelper.getProviderFormattedString(any(Resources.class), anyInt())).thenCallRealMethod();
- }
-
public static void mockProviderApiConnector(final BackendMockProvider.TestBackendErrorCase errorCase) throws IOException {
BackendMockProvider.provideBackendResponsesFor(errorCase);
}
@@ -569,6 +561,8 @@ public class MockHelper {
thenReturn(String.format(errorMessages.getString("warning_expired_provider_cert"), "Bitmask"));
when(mockedResources.getString(eq(R.string.setup_error_text), anyString())).
thenReturn(String.format(errorMessages.getString("setup_error_text"), "Bitmask"));
+ when(mockedResources.getString(eq(R.string.setup_error_text_custom), anyString())).
+ thenReturn(String.format(errorMessages.getString("setup_error_text_custom"), "RiseupVPN"));
when(mockedResources.getString(R.string.app_name)).
thenReturn("Bitmask");
when(mockedResources.getString(eq(R.string.error_tor_timeout), anyString())).
diff --git a/app/src/androidTest/legacy/TestCalendarProvider.java b/app/src/test/java/se/leap/bitmaskclient/testutils/TestCalendarProvider.java
index 82ea8b59..ea202ab4 100644
--- a/app/src/androidTest/legacy/TestCalendarProvider.java
+++ b/app/src/test/java/se/leap/bitmaskclient/testutils/TestCalendarProvider.java
@@ -1,4 +1,4 @@
-package se.leap.bitmaskclient.test;
+package se.leap.bitmaskclient.testutils;
import java.util.Calendar;
@@ -8,7 +8,7 @@ import se.leap.bitmaskclient.eip.CalendarProviderInterface;
* Created by cyberta on 13.09.17.
*/
-class TestCalendarProvider implements CalendarProviderInterface {
+public class TestCalendarProvider implements CalendarProviderInterface {
private long currentTimeInMillis = 0;
diff --git a/app/src/test/resources/error_messages.json b/app/src/test/resources/error_messages.json
index ae04bdb0..f5e2d83c 100644
--- a/app/src/test/resources/error_messages.json
+++ b/app/src/test/resources/error_messages.json
@@ -14,6 +14,7 @@
"warning_corrupted_provider_cert": "Stored provider certificate is invalid. You can either update %s (recommended) or update the provider certificate using a commercial CA certificate.",
"warning_expired_provider_cert": "Stored provider certificate is expired. You can either update %s (recommended) or update the provider certificate using a commercial CA certificate.",
"setup_error_text": "There was an error configuring %s with your chosen provider.",
+ "setup_error_text_custom": "There was an error configuring %s.",
"error_tor_timeout": "Starting bridges failed. Do you want to retry or continue with an unobfuscated secure connection to configure %s?",
"error_network_connection": "%s has no internet connection. Please check your WiFi and cellular data settings."
} \ No newline at end of file
diff --git a/app/src/test/resources/float.hexacab.org.pem b/app/src/test/resources/float.hexacab.org.pem
new file mode 100644
index 00000000..96306a8c
--- /dev/null
+++ b/app/src/test/resources/float.hexacab.org.pem
@@ -0,0 +1,42 @@
+-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBZMRgwFgYDVQQKDA9SaXNl
+dXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UE
+AwwXUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EwHhcNMTQwNDI4MDAwMDAwWhcNMjQw
+NDI4MDAwMDAwWjBZMRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsM
+Emh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UEAwwXUmlzZXVwIE5ldHdvcmtzIFJv
+b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC76J4ciMJ8Sg0m
+TP7DF2DT9zNe0Csk4myoMFC57rfJeqsAlJCv1XMzBmXrw8wq/9z7XHv6n/0sWU7a
+7cF2hLR33ktjwODlx7vorU39/lXLndo492ZBhXQtG1INMShyv+nlmzO6GT7ESfNE
+LliFitEzwIegpMqxCIHXFuobGSCWF4N0qLHkq/SYUMoOJ96O3hmPSl1kFDRMtWXY
+iw1SEKjUvpyDJpVs3NGxeLCaA7bAWhDY5s5Yb2fA1o8ICAqhowurowJpW7n5ZuLK
+5VNTlNy6nZpkjt1QycYvNycffyPOFm/Q/RKDlvnorJIrihPkyniV3YY5cGgP+Qkx
+HUOT0uLA6LHtzfiyaOqkXwc4b0ZcQD5Vbf6Prd20Ppt6ei0zazkUPwxld3hgyw58
+m/4UIjG3PInWTNf293GngK2Bnz8Qx9e/6TueMSAn/3JBLem56E0WtmbLVjvko+LF
+PM5xA+m0BmuSJtrD1MUCXMhqYTtiOvgLBlUm5zkNxALzG+cXB28k6XikXt6MRG7q
+hzIPG38zwkooM55yy5i1YfcIi5NjMH6A+t4IJxxwb67MSb6UFOwg5kFokdONZcwj
+shczHdG9gLKSBIvrKa03Nd3W2dF9hMbRu//STcQxOailDBQCnXXfAATj9pYzdY4k
+ha8VCAREGAKTDAex9oXf1yRuktES4QIDAQABo2AwXjAdBgNVHQ4EFgQUC4tdmLVu
+f9hwfK4AGliaet5KkcgwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMBAf8wHwYD
+VR0jBBgwFoAUC4tdmLVuf9hwfK4AGliaet5KkcgwDQYJKoZIhvcNAQENBQADggIB
+AGzL+GRnYu99zFoy0bXJKOGCF5XUXP/3gIXPRDqQf5g7Cu/jYMID9dB3No4Zmf7v
+qHjiSXiS8jx1j/6/Luk6PpFbT7QYm4QLs1f4BlfZOti2KE8r7KRDPIecUsUXW6P/
+3GJAVYH/+7OjA39za9AieM7+H5BELGccGrM5wfl7JeEz8in+V2ZWDzHQO4hMkiTQ
+4ZckuaL201F68YpiItBNnJ9N5nHr1MRiGyApHmLXY/wvlrOpclh95qn+lG6/2jk7
+3AmihLOKYMlPwPakJg4PYczm3icFLgTpjV5sq2md9bRyAg3oPGfAuWHmKj2Ikqch
+Td5CHKGxEEWbGUWEMP0s1A/JHWiCbDigc4Cfxhy56CWG4q0tYtnc2GMw8OAUO6Wf
+Xu5pYKNkzKSEtT/MrNJt44tTZWbKV/Pi/N2Fx36my7TgTUj7g3xcE9eF4JV2H/sg
+tsK3pwE0FEqGnT4qMFbixQmc8bGyuakr23wjMvfO7eZUxBuWYR2SkcP26sozF9PF
+tGhbZHQVGZUTVPyvwahMUEhbPGVerOW0IYpxkm0x/eaWdTc4vPpf/rIlgbAjarnJ
+UN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp
+0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIBYjCCAQigAwIBAgIBATAKBggqhkjOPQQDAjAXMRUwEwYDVQQDEwxMRUFQIFJv
+b3QgQ0EwHhcNMjExMTAyMTkwNTM3WhcNMjYxMTAyMTkxMDM3WjAXMRUwEwYDVQQD
+EwxMRUFQIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQxOXBGu+gf
+pjHzVteGTWL6XnFxtEnKMFpKaJkA/VOHmESzoLsZRQxt88GssxaqC01J17idQiqv
+zgNpedmtvFtyo0UwQzAOBgNVHQ8BAf8EBAMCAqQwEgYDVR0TAQH/BAgwBgEB/wIB
+ATAdBgNVHQ4EFgQUZdoUlJrCIUNFrpffAq+LQjnwEz4wCgYIKoZIzj0EAwIDSAAw
+RQIgfr3w4tnRG+NdI3LsGPlsRktGK20xHTzsB3orB0yC6cICIQCB+/9y8nmSStfN
+VUMUyk2hNd7/kC8nL222TTD7VZUtsg==
+-----END CERTIFICATE----- \ No newline at end of file