diff options
Diffstat (limited to 'app')
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 |