summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2021-12-16 23:45:41 +0100
committercyBerta <cyberta@riseup.net>2021-12-17 01:09:57 +0100
commit8411cd82c0572e0e871c1cf93e0d4c05b35fb999 (patch)
treecb659af65a1b3baef5285a395c3cdfa2251c8187 /app/src
parent8e5ce3e312f03035314b6ab036c625f83a515fc7 (diff)
allow to parse and handle multiple certs in a pem file
Diffstat (limited to 'app/src')
-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/providersetup/ProviderApiManagerBase.java50
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java10
-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.json (renamed from app/src/normal/assets/float.hexacab.org.json)0
-rw-r--r--app/src/normalProductionFatDebug/assets/float.hexacab.org.pem (renamed from app/src/normal/assets/float.hexacab.org.pem)0
-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.url (renamed from app/src/normal/assets/urls/float.hexacab.org.url)0
-rw-r--r--app/src/normalProductionFatDebug/assets/urls/riseup.net.url6
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java2
-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.java5
-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/float.hexacab.org.pem42
19 files changed, 350 insertions, 120 deletions
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 dccb5678..0a81b9cb 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
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.Looper;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -37,6 +38,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,10 +46,13 @@ 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;
+import de.blinkt.openvpn.core.VpnStatus;
import se.leap.bitmaskclient.BuildConfig;
import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.providersetup.ProviderAPI;
@@ -99,25 +104,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.replaceAll("-----BEGIN CERTIFICATE-----", "").trim().replaceAll("-----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();
+ 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/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/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/normal/assets/float.hexacab.org.json b/app/src/normalProductionFatDebug/assets/float.hexacab.org.json
index d5ba9819..d5ba9819 100644
--- a/app/src/normal/assets/float.hexacab.org.json
+++ b/app/src/normalProductionFatDebug/assets/float.hexacab.org.json
diff --git a/app/src/normal/assets/float.hexacab.org.pem b/app/src/normalProductionFatDebug/assets/float.hexacab.org.pem
index 96306a8c..96306a8c 100644
--- a/app/src/normal/assets/float.hexacab.org.pem
+++ b/app/src/normalProductionFatDebug/assets/float.hexacab.org.pem
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/normal/assets/urls/float.hexacab.org.url b/app/src/normalProductionFatDebug/assets/urls/float.hexacab.org.url
index 675a01dd..675a01dd 100644
--- a/app/src/normal/assets/urls/float.hexacab.org.url
+++ b/app/src/normalProductionFatDebug/assets/urls/float.hexacab.org.url
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/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
index d6ee0def..f3fd5dbb 100644
--- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
@@ -322,6 +322,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 +344,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());
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..f80f41ed 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,7 +412,7 @@ 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();
@@ -507,7 +506,7 @@ 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();
}
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/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