From ac7204c4daa0ae7e68ddfb89845c4f115b8a646c Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sun, 18 Mar 2018 03:37:20 +0100 Subject: refactor ConfigHelper, split into multiple helper classes --- .../java/se/leap/bitmaskclient/ConfigHelper.java | 445 --------------------- .../java/se/leap/bitmaskclient/LeapSRPSession.java | 14 +- .../java/se/leap/bitmaskclient/MainActivity.java | 9 +- .../main/java/se/leap/bitmaskclient/Provider.java | 4 +- .../leap/bitmaskclient/ProviderApiManagerBase.java | 16 +- .../java/se/leap/bitmaskclient/StartActivity.java | 10 +- .../leap/bitmaskclient/TLSCompatSocketFactory.java | 1 + .../drawer/NavigationDrawerFragment.java | 20 +- .../bitmaskclient/eip/VpnCertificateValidator.java | 2 +- .../bitmaskclient/fragments/AlwaysOnDialog.java | 3 +- .../se/leap/bitmaskclient/utils/ConfigHelper.java | 161 ++++++++ .../se/leap/bitmaskclient/utils/FileHelper.java | 22 + .../bitmaskclient/utils/InputStreamHelper.java | 21 + .../leap/bitmaskclient/utils/KeyStoreHelper.java | 79 ++++ .../leap/bitmaskclient/utils/PreferenceHelper.java | 220 ++++++++++ 15 files changed, 552 insertions(+), 475 deletions(-) delete mode 100644 app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/utils/FileHelper.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/utils/InputStreamHelper.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java (limited to 'app/src/main/java/se/leap') diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java deleted file mode 100644 index aaff9ebc..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java +++ /dev/null @@ -1,445 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package se.leap.bitmaskclient; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Log; - -import org.json.JSONException; -import org.json.JSONObject; -import org.spongycastle.util.encoders.Base64; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.math.BigInteger; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPrivateKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import static android.R.attr.name; -import static se.leap.bitmaskclient.Constants.ALWAYS_ON_SHOW_DIALOG; -import static se.leap.bitmaskclient.Constants.DEFAULT_SHARED_PREFS_BATTERY_SAVER; -import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION; -import static se.leap.bitmaskclient.Constants.PROVIDER_CONFIGURED; -import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION; -import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; -import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; - -/** - * Stores constants, and implements auxiliary methods used across all Bitmask Android classes. - * - * @author parmegv - * @author MeanderingCode - */ -public class ConfigHelper { - private static final String TAG = ConfigHelper.class.getName(); - private static KeyStore keystore_trusted; - - final public static String NG_1024 = - "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3"; - final public static BigInteger G = new BigInteger("2"); - - public static boolean checkErroneousDownload(String downloadedString) { - try { - if (downloadedString == null || downloadedString.isEmpty() || new JSONObject(downloadedString).has(ProviderAPI.ERRORS) || new JSONObject(downloadedString).has(ProviderAPI.BACKEND_ERROR_KEY)) { - return true; - } else { - return false; - } - } catch (NullPointerException | JSONException e) { - return false; - } - } - - /** - * Treat the input as the MSB representation of a number, - * and lop off leading zero elements. For efficiency, the - * input is simply returned if no leading zeroes are found. - * - * @param in array to be trimmed - */ - public static byte[] trim(byte[] in) { - if (in.length == 0 || in[0] != 0) - return in; - - int len = in.length; - int i = 1; - while (in[i] == 0 && i < len) - ++i; - byte[] ret = new byte[len - i]; - System.arraycopy(in, i, ret, 0, len - i); - return ret; - } - - public static X509Certificate parseX509CertificateFromString(String certificateString) { - java.security.cert.Certificate certificate = null; - 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(); - } - } catch (NullPointerException | CertificateException | IOException | IllegalArgumentException e) { - return null; - } - return (X509Certificate) certificate; - } - - public static String loadInputStreamAsString(java.io.InputStream is) { - java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); - return s.hasNext() ? s.next() : ""; - } - - //allows us to mock FileInputStream - public static InputStream getInputStreamFrom(String filePath) throws FileNotFoundException { - return new FileInputStream(filePath); - } - - protected static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) { - RSAPrivateKey key; - try { - KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); - rsaKeyString = rsaKeyString.replaceFirst("-----BEGIN RSA PRIVATE KEY-----", "").replaceFirst("-----END RSA PRIVATE KEY-----", ""); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(rsaKeyString)); - key = (RSAPrivateKey) kf.generatePrivate(keySpec); - } catch (InvalidKeySpecException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } catch (NoSuchAlgorithmException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } catch (NoSuchProviderException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } catch (NullPointerException e) { - e.printStackTrace(); - return null; - } - - return key; - } - - private static String byteArrayToHex(byte[] input) { - int readBytes = input.length; - StringBuffer hexData = new StringBuffer(); - int onebyte; - for (int i = 0; i < readBytes; i++) { - onebyte = ((0x000000ff & input[i]) | 0xffffff00); - hexData.append(Integer.toHexString(onebyte).substring(6)); - } - return hexData.toString(); - } - - /** - * Calculates the hexadecimal representation of a sha256/sha1 fingerprint of a certificate - * - * @param certificate - * @param encoding - * @return - * @throws NoSuchAlgorithmException - * @throws CertificateEncodingException - */ - @NonNull - public static String getFingerprintFromCertificate(X509Certificate certificate, String encoding) throws NoSuchAlgorithmException, CertificateEncodingException /*, UnsupportedEncodingException*/ { - byte[] byteArray = MessageDigest.getInstance(encoding).digest(certificate.getEncoded()); - return byteArrayToHex(byteArray); - } - - /** - * 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); - keystore_trusted.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 (keystore_trusted == null) { - keystore_trusted = KeyStore.getInstance("BKS"); - keystore_trusted.load(null); - } - keystore_trusted.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 keystore_trusted; - } - - - public static boolean providerInSharedPreferences(@NonNull SharedPreferences preferences) { - return preferences.getBoolean(PROVIDER_CONFIGURED, false); - } - - public static Provider getSavedProviderFromSharedPreferences(@NonNull SharedPreferences preferences) { - Provider provider = new Provider(); - try { - provider.setMainUrl(new URL(preferences.getString(Provider.MAIN_URL, ""))); - provider.define(new JSONObject(preferences.getString(Provider.KEY, ""))); - provider.setCaCert(preferences.getString(Provider.CA_CERT, "")); - provider.setVpnCertificate(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); - provider.setPrivateKey(preferences.getString(PROVIDER_PRIVATE_KEY, "")); - provider.setEipServiceJson(new JSONObject(preferences.getString(PROVIDER_EIP_DEFINITION, ""))); - } catch (MalformedURLException | JSONException e) { - e.printStackTrace(); - } - - return provider; - } - - public static String getFromPersistedProvider(String toFetch, String providerDomain, SharedPreferences preferences) { - return preferences.getString(toFetch + "." + providerDomain, ""); - } - - public static String getProviderName(String provider) { - return getProviderName(null, provider); - } - - public static String getProviderName(@Nullable SharedPreferences preferences) { - return getProviderName(preferences,null); - } - - public static String getProviderName(@Nullable SharedPreferences preferences, @Nullable String provider) { - if (provider == null && preferences != null) { - provider = preferences.getString(Provider.KEY, ""); - } - try { - JSONObject providerJson = new JSONObject(provider); - String lang = Locale.getDefault().getLanguage(); - return providerJson.getJSONObject(Provider.NAME).getString(lang); - } catch (JSONException e) { - try { - JSONObject providerJson = new JSONObject(provider); - return providerJson.getJSONObject(Provider.NAME).getString("en"); - } catch (JSONException e2) { - return null; - } - } catch (NullPointerException npe) { - return null; - } - } - - public static String getProviderDomain(SharedPreferences preferences) { - return getProviderDomain(preferences, null); - } - - public static String getProviderDomain(String provider) { - return getProviderDomain(null, provider); - } - - public static String getProviderDomain(@Nullable SharedPreferences preferences, @Nullable String provider) { - if (provider == null && preferences != null) { - provider = preferences.getString(Provider.KEY, ""); - } - try { - JSONObject providerJson = new JSONObject(provider); - return providerJson.getString(Provider.DOMAIN); - } catch (JSONException | NullPointerException e) { - return null; - } - } - - public static String getDescription(SharedPreferences preferences) { - try { - JSONObject providerJson = new JSONObject(preferences.getString(Provider.KEY, "")); - String lang = Locale.getDefault().getLanguage(); - return providerJson.getJSONObject(Provider.DESCRIPTION).getString(lang); - } catch (JSONException e) { - try { - JSONObject providerJson = new JSONObject(preferences.getString(Provider.KEY, "")); - return providerJson.getJSONObject(Provider.DESCRIPTION).getString("en"); - } catch (JSONException e1) { - return null; - } - } - } - - // TODO: replace commit with apply after refactoring EIP - //FIXME: don't save private keys in shared preferences! use the keystore - public static void storeProviderInPreferences(SharedPreferences preferences, Provider provider) { - preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). - putString(Provider.MAIN_URL, provider.getMainUrlString()). - putString(Provider.KEY, provider.getDefinitionString()). - putString(Provider.CA_CERT, provider.getCaCert()). - putString(PROVIDER_EIP_DEFINITION, provider.getEipServiceJsonString()). - putString(PROVIDER_PRIVATE_KEY, provider.getPrivateKey()). - putString(PROVIDER_VPN_CERTIFICATE, provider.getVpnCertificate()). - commit(); - - String providerDomain = provider.getDomain(); - preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). - putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrlString()). - putString(Provider.KEY + "." + providerDomain, provider.getDefinitionString()). - putString(Provider.CA_CERT + "." + providerDomain, provider.getCaCert()). - putString(PROVIDER_EIP_DEFINITION + "." + providerDomain, provider.getEipServiceJsonString()). - apply(); - } - - - public static void clearDataOfLastProvider(SharedPreferences preferences) { - clearDataOfLastProvider(preferences, false); - } - - @Deprecated - public static void clearDataOfLastProvider(SharedPreferences preferences, boolean commit) { - Map allEntries = preferences.getAll(); - List lastProvidersKeys = new ArrayList<>(); - for (Map.Entry entry : allEntries.entrySet()) { - //sort out all preferences that don't belong to the last provider - if (entry.getKey().startsWith(Provider.KEY + ".") || - entry.getKey().startsWith(Provider.CA_CERT + ".") || - entry.getKey().startsWith(Provider.CA_CERT_FINGERPRINT + "." )|| - entry.getKey().equals(PREFERENCES_APP_VERSION) - ) { - continue; - } - lastProvidersKeys.add(entry.getKey()); - } - - SharedPreferences.Editor preferenceEditor = preferences.edit(); - for (String key : lastProvidersKeys) { - preferenceEditor.remove(key); - } - if (commit) { - preferenceEditor.commit(); - } else { - preferenceEditor.apply(); - } - } - - public static void deleteProviderDetailsFromPreferences(@NonNull SharedPreferences preferences, String providerDomain) { - preferences.edit(). - remove(Provider.KEY + "." + providerDomain). - remove(Provider.CA_CERT + "." + providerDomain). - remove(Provider.CA_CERT_FINGERPRINT + "." + providerDomain). - remove(Provider.MAIN_URL + "." + providerDomain). - remove(Provider.KEY + "." + providerDomain). - remove(Provider.CA_CERT + "." + providerDomain). - remove(PROVIDER_EIP_DEFINITION + "." + providerDomain). - remove(PROVIDER_PRIVATE_KEY + "." + providerDomain). - remove(PROVIDER_VPN_CERTIFICATE + "." + providerDomain). - apply(); - } - - public static void saveBattery(Context context, boolean isEnabled) { - if (context == null) { - return; - } - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - preferences.edit().putBoolean(DEFAULT_SHARED_PREFS_BATTERY_SAVER, isEnabled).apply(); - } - - public static boolean getSaveBattery(Context context) { - if (context == null) { - return false; - } - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - return preferences.getBoolean(DEFAULT_SHARED_PREFS_BATTERY_SAVER, false); - } - - public static void saveShowAlwaysOnDialog(Context context, boolean showAlwaysOnDialog) { - if (context == null) { - return; - } - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); - preferences.edit().putBoolean(ALWAYS_ON_SHOW_DIALOG, showAlwaysOnDialog).apply(); - } - - public static boolean getShowAlwaysOnDialog(Context context) { - if (context == null) { - return true; - } - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); - return preferences.getBoolean(ALWAYS_ON_SHOW_DIALOG, true); - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java index 3a1fd6e0..d1f1ed21 100644 --- a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java +++ b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java @@ -17,12 +17,16 @@ package se.leap.bitmaskclient; -import org.jboss.security.srp.*; +import org.jboss.security.srp.SRPParameters; -import java.io.*; -import java.math.*; -import java.security.*; -import java.util.*; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; + +import se.leap.bitmaskclient.utils.ConfigHelper; /** * Implements all SRP algorithm logic. diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java index 4d57b6bc..8096607a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java @@ -51,6 +51,7 @@ import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.eip.VoidVpnService; import se.leap.bitmaskclient.fragments.LogFragment; +import se.leap.bitmaskclient.utils.ConfigHelper; import static android.content.Intent.CATEGORY_DEFAULT; import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT; @@ -76,6 +77,8 @@ import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTI import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE; import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; +import static se.leap.bitmaskclient.utils.PreferenceHelper.storeProviderInPreferences; public class MainActivity extends AppCompatActivity implements Observer { @@ -123,7 +126,7 @@ public class MainActivity extends AppCompatActivity implements Observer { getSupportFragmentManager().findFragmentById(R.id.navigation_drawer); preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); - provider = ConfigHelper.getSavedProviderFromSharedPreferences(preferences); + provider = getSavedProviderFromSharedPreferences(preferences); // Set up the drawer. navigationDrawerFragment.setUp( @@ -194,7 +197,7 @@ public class MainActivity extends AppCompatActivity implements Observer { return; } - ConfigHelper.storeProviderInPreferences(preferences, provider); + storeProviderInPreferences(preferences, provider); navigationDrawerFragment.refresh(); switch (requestCode) { @@ -321,7 +324,7 @@ public class MainActivity extends AppCompatActivity implements Observer { case CORRECTLY_DOWNLOADED_VPN_CERTIFICATE: provider = resultData.getParcelable(PROVIDER_KEY); - ConfigHelper.storeProviderInPreferences(preferences, provider); + storeProviderInPreferences(preferences, provider); EipCommand.startVPN(this, true); break; case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java index fd067bf9..5e5a3a62 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -140,7 +140,7 @@ public final class Provider implements Parcelable { return definition; } - String getDefinitionString() { + public String getDefinitionString() { return getDefinition().toString(); } @@ -148,7 +148,7 @@ public final class Provider implements Parcelable { return mainUrl.getDomain(); } - String getMainUrlString() { + public String getMainUrlString() { return getMainUrl().toString(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java index 2cde431e..25b5a56c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java @@ -52,8 +52,9 @@ import javax.net.ssl.SSLHandshakeException; import okhttp3.OkHttpClient; import se.leap.bitmaskclient.Constants.CREDENTIAL_ERRORS; +import se.leap.bitmaskclient.utils.ConfigHelper; -import static se.leap.bitmaskclient.ConfigHelper.getFingerprintFromCertificate; +import static se.leap.bitmaskclient.utils.ConfigHelper.getFingerprintFromCertificate; import static se.leap.bitmaskclient.Constants.BROADCAST_PROVIDER_API_EVENT; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; @@ -103,6 +104,9 @@ import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; 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.R.string.warning_expired_provider_cert; +import static se.leap.bitmaskclient.utils.ConfigHelper.parseRsaKeyFromString; +import static se.leap.bitmaskclient.utils.PreferenceHelper.deleteProviderDetailsFromPreferences; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getFromPersistedProvider; /** * Implements the logic of the http api calls. The methods of this class needs to be called from @@ -218,7 +222,7 @@ public abstract class ProviderApiManagerBase { void resetProviderDetails(Provider provider) { provider.reset(); - ConfigHelper.deleteProviderDetailsFromPreferences(preferences, provider.getDomain()); + deleteProviderDetailsFromPreferences(preferences, provider.getDomain()); } String formatErrorMessage(final int toastStringId) { @@ -754,16 +758,16 @@ public abstract class ProviderApiManagerBase { } protected String getPersistedPrivateKey(String providerDomain) { - return ConfigHelper.getFromPersistedProvider(PROVIDER_PRIVATE_KEY, providerDomain, preferences); + return getFromPersistedProvider(PROVIDER_PRIVATE_KEY, providerDomain, preferences); } protected String getPersistedVPNCertificate(String providerDomain) { - return ConfigHelper.getFromPersistedProvider(PROVIDER_VPN_CERTIFICATE, providerDomain, preferences); + return getFromPersistedProvider(PROVIDER_VPN_CERTIFICATE, providerDomain, preferences); } protected JSONObject getPersistedProviderDefinition(String providerDomain) { try { - return new JSONObject(ConfigHelper.getFromPersistedProvider(Provider.KEY, providerDomain, preferences)); + return new JSONObject(getFromPersistedProvider(Provider.KEY, providerDomain, preferences)); } catch (JSONException e) { e.printStackTrace(); return new JSONObject(); @@ -849,7 +853,7 @@ public abstract class ProviderApiManagerBase { } } - RSAPrivateKey key = ConfigHelper.parseRsaKeyFromString(keyString); + RSAPrivateKey key = parseRsaKeyFromString(keyString); keyString = Base64.encodeToString(key.getEncoded(), Base64.DEFAULT); provider.setPrivateKey( "-----BEGIN RSA PRIVATE KEY-----\n" + keyString + "-----END RSA PRIVATE KEY-----"); diff --git a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java index 6bbdeb4f..33c13b90 100644 --- a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java @@ -15,6 +15,7 @@ import java.lang.annotation.RetentionPolicy; import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.userstatus.User; +import se.leap.bitmaskclient.utils.ConfigHelper; import static se.leap.bitmaskclient.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; @@ -24,6 +25,9 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; +import static se.leap.bitmaskclient.utils.PreferenceHelper.providerInSharedPreferences; +import static se.leap.bitmaskclient.utils.PreferenceHelper.storeProviderInPreferences; /** * Activity shown at startup. Evaluates if App is started for the first time or has been upgraded @@ -154,9 +158,9 @@ public class StartActivity extends Activity{ } private void prepareEIP() { - boolean provider_exists = ConfigHelper.providerInSharedPreferences(preferences); + boolean provider_exists = providerInSharedPreferences(preferences); if (provider_exists) { - Provider provider = ConfigHelper.getSavedProviderFromSharedPreferences(preferences); + Provider provider = getSavedProviderFromSharedPreferences(preferences); if(!provider.isConfigured()) { configureLeapProvider(); } else { @@ -186,7 +190,7 @@ public class StartActivity extends Activity{ if (requestCode == REQUEST_CODE_CONFIGURE_LEAP) { if (resultCode == RESULT_OK && data != null && data.hasExtra(Provider.KEY)) { Provider provider = data.getParcelableExtra(Provider.KEY); - ConfigHelper.storeProviderInPreferences(preferences, provider); + storeProviderInPreferences(preferences, provider); EipCommand.startVPN(this, false); showMainActivity(); } else if (resultCode == RESULT_CANCELED) { diff --git a/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java b/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java index 76d38447..cca75bdf 100644 --- a/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java +++ b/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java @@ -22,6 +22,7 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import okhttp3.OkHttpClient; +import se.leap.bitmaskclient.utils.ConfigHelper; /** * Created by cyberta on 24.10.17. diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java index 9256c136..badc85ea 100644 --- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -46,9 +46,8 @@ import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.CompoundButton; import android.widget.ListView; -import android.widget.Toast; -import se.leap.bitmaskclient.ConfigHelper; +import se.leap.bitmaskclient.utils.ConfigHelper; import se.leap.bitmaskclient.DrawerSettingsAdapter; import se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem; import se.leap.bitmaskclient.FragmentManagerEnhanced; @@ -62,8 +61,6 @@ import se.leap.bitmaskclient.fragments.LogFragment; import static android.content.Context.MODE_PRIVATE; import static se.leap.bitmaskclient.BitmaskApp.getRefWatcher; -import static se.leap.bitmaskclient.ConfigHelper.getSaveBattery; -import static se.leap.bitmaskclient.ConfigHelper.getShowAlwaysOnDialog; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; @@ -77,6 +74,11 @@ import static se.leap.bitmaskclient.DrawerSettingsAdapter.SWITCH_PROVIDER; import static se.leap.bitmaskclient.R.string.about_fragment_title; import static se.leap.bitmaskclient.R.string.log_fragment_title; import static se.leap.bitmaskclient.R.string.switch_provider_menu_option; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getProviderName; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getSaveBattery; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getShowAlwaysOnDialog; +import static se.leap.bitmaskclient.utils.PreferenceHelper.saveBattery; /** * Fragment used for managing interactions for and presentation of a navigation drawer. @@ -307,7 +309,7 @@ public class NavigationDrawerFragment extends Fragment { DrawerSettingsItem item = settingsListAdapter.getDrawerItem(BATTERY_SAVER); item.setChecked(true); settingsListAdapter.notifyDataSetChanged(); - ConfigHelper.saveBattery(getContext(), item.isChecked()); + saveBattery(getContext(), item.isChecked()); } }) .setNegativeButton(activity.getString(android.R.string.no), new DialogInterface.OnClickListener() { @@ -392,14 +394,14 @@ public class NavigationDrawerFragment extends Fragment { private void onSwitchItemSelected(int elementType, boolean newStateIsChecked) { switch (elementType) { case BATTERY_SAVER: - if (ConfigHelper.getSaveBattery(getContext()) == newStateIsChecked) { + if (getSaveBattery(getContext()) == newStateIsChecked) { //initial ui setup, ignore return; } if (newStateIsChecked) { showExperimentalFeatureAlert(); } else { - ConfigHelper.saveBattery(this.getContext(), false); + saveBattery(this.getContext(), false); disableSwitch(BATTERY_SAVER); } break; @@ -423,7 +425,7 @@ public class NavigationDrawerFragment extends Fragment { mTitle = getString(R.string.vpn_fragment_title); fragment = new EipFragment(); Bundle arguments = new Bundle(); - Provider currentProvider = ConfigHelper.getSavedProviderFromSharedPreferences(preferences); + Provider currentProvider = getSavedProviderFromSharedPreferences(preferences); arguments.putParcelable(PROVIDER_KEY, currentProvider); fragment.setArguments(arguments); } else { @@ -481,7 +483,7 @@ public class NavigationDrawerFragment extends Fragment { private void createListAdapterData() { accountListAdapter.clear(); - String providerName = ConfigHelper.getProviderName(preferences); + String providerName = getProviderName(preferences); if (providerName == null) { //TODO: ADD A header to the ListView containing a useful message. //TODO 2: disable switchProvider 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 03dd9d05..83904729 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java @@ -22,7 +22,7 @@ import java.security.cert.X509Certificate; import java.util.Calendar; import java.util.Date; -import se.leap.bitmaskclient.ConfigHelper; +import se.leap.bitmaskclient.utils.ConfigHelper; public class VpnCertificateValidator { public final static String TAG = VpnCertificateValidator.class.getSimpleName(); diff --git a/app/src/main/java/se/leap/bitmaskclient/fragments/AlwaysOnDialog.java b/app/src/main/java/se/leap/bitmaskclient/fragments/AlwaysOnDialog.java index 3558f378..e3d004f5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/fragments/AlwaysOnDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/fragments/AlwaysOnDialog.java @@ -17,7 +17,8 @@ import butterknife.InjectView; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.views.IconTextView; -import static se.leap.bitmaskclient.ConfigHelper.saveShowAlwaysOnDialog; +import static se.leap.bitmaskclient.utils.PreferenceHelper.saveShowAlwaysOnDialog; + /** * Created by cyberta on 25.02.18. diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java new file mode 100644 index 00000000..bb9fb2d4 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient.utils; + +import android.support.annotation.NonNull; + +import org.json.JSONException; +import org.json.JSONObject; +import org.spongycastle.util.encoders.Base64; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; + +import se.leap.bitmaskclient.ProviderAPI; + +/** + * Stores constants, and implements auxiliary methods used across all Bitmask Android classes. + * + * @author parmegv + * @author MeanderingCode + */ +public class ConfigHelper { + final public static String NG_1024 = + "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3"; + final public static BigInteger G = new BigInteger("2"); + + public static boolean checkErroneousDownload(String downloadedString) { + try { + if (downloadedString == null || downloadedString.isEmpty() || new JSONObject(downloadedString).has(ProviderAPI.ERRORS) || new JSONObject(downloadedString).has(ProviderAPI.BACKEND_ERROR_KEY)) { + return true; + } else { + return false; + } + } catch (NullPointerException | JSONException e) { + return false; + } + } + + /** + * Treat the input as the MSB representation of a number, + * and lop off leading zero elements. For efficiency, the + * input is simply returned if no leading zeroes are found. + * + * @param in array to be trimmed + */ + public static byte[] trim(byte[] in) { + if (in.length == 0 || in[0] != 0) + return in; + + int len = in.length; + int i = 1; + while (in[i] == 0 && i < len) + ++i; + byte[] ret = new byte[len - i]; + System.arraycopy(in, i, ret, 0, len - i); + return ret; + } + + public static X509Certificate parseX509CertificateFromString(String certificateString) { + java.security.cert.Certificate certificate = null; + 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(); + } + } catch (NullPointerException | CertificateException | IOException | IllegalArgumentException e) { + return null; + } + return (X509Certificate) certificate; + } + + public static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) { + RSAPrivateKey key; + try { + KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); + rsaKeyString = rsaKeyString.replaceFirst("-----BEGIN RSA PRIVATE KEY-----", "").replaceFirst("-----END RSA PRIVATE KEY-----", ""); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(rsaKeyString)); + key = (RSAPrivateKey) kf.generatePrivate(keySpec); + } catch (InvalidKeySpecException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } catch (NoSuchProviderException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } catch (NullPointerException e) { + e.printStackTrace(); + return null; + } + + return key; + } + + private static String byteArrayToHex(byte[] input) { + int readBytes = input.length; + StringBuffer hexData = new StringBuffer(); + int onebyte; + for (int i = 0; i < readBytes; i++) { + onebyte = ((0x000000ff & input[i]) | 0xffffff00); + hexData.append(Integer.toHexString(onebyte).substring(6)); + } + return hexData.toString(); + } + + /** + * Calculates the hexadecimal representation of a sha256/sha1 fingerprint of a certificate + * + * @param certificate + * @param encoding + * @return + * @throws NoSuchAlgorithmException + * @throws CertificateEncodingException + */ + @NonNull + public static String getFingerprintFromCertificate(X509Certificate certificate, String encoding) throws NoSuchAlgorithmException, CertificateEncodingException /*, UnsupportedEncodingException*/ { + byte[] byteArray = MessageDigest.getInstance(encoding).digest(certificate.getEncoded()); + return byteArrayToHex(byteArray); + } + + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/FileHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/FileHelper.java new file mode 100644 index 00000000..1c3e1ebb --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/utils/FileHelper.java @@ -0,0 +1,22 @@ +package se.leap.bitmaskclient.utils; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +/** + * Created by cyberta on 18.03.18. + */ + +public class FileHelper { + public static File createFile(File dir, String fileName) { + return new File(dir, fileName); + } + + public static void persistFile(File file, String content) throws IOException { + FileWriter writer = new FileWriter(file); + writer.write(content); + writer.close(); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/InputStreamHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/InputStreamHelper.java new file mode 100644 index 00000000..87996615 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/utils/InputStreamHelper.java @@ -0,0 +1,21 @@ +package se.leap.bitmaskclient.utils; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +/** + * Created by cyberta on 18.03.18. + */ + +public class InputStreamHelper { + //allows us to mock FileInputStream + public static InputStream getInputStreamFrom(String filePath) throws FileNotFoundException { + return new FileInputStream(filePath); + } + + public static String loadInputStreamAsString(InputStream is) { + java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java new file mode 100644 index 00000000..0cc9687b --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java @@ -0,0 +1,79 @@ +package se.leap.bitmaskclient.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +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 keystore_trusted; + + /** + * 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); + keystore_trusted.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 (keystore_trusted == null) { + keystore_trusted = KeyStore.getInstance("BKS"); + keystore_trusted.load(null); + } + keystore_trusted.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 keystore_trusted; + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java new file mode 100644 index 00000000..92f025b2 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java @@ -0,0 +1,220 @@ +package se.leap.bitmaskclient.utils; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import se.leap.bitmaskclient.Provider; + +import static se.leap.bitmaskclient.Constants.ALWAYS_ON_SHOW_DIALOG; +import static se.leap.bitmaskclient.Constants.DEFAULT_SHARED_PREFS_BATTERY_SAVER; +import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION; +import static se.leap.bitmaskclient.Constants.PROVIDER_CONFIGURED; +import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION; +import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; +import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; + +/** + * Created by cyberta on 18.03.18. + */ + +public class PreferenceHelper { + public static boolean providerInSharedPreferences(@NonNull SharedPreferences preferences) { + return preferences.getBoolean(PROVIDER_CONFIGURED, false); + } + + public static Provider getSavedProviderFromSharedPreferences(@NonNull SharedPreferences preferences) { + Provider provider = new Provider(); + try { + provider.setMainUrl(new URL(preferences.getString(Provider.MAIN_URL, ""))); + provider.define(new JSONObject(preferences.getString(Provider.KEY, ""))); + provider.setCaCert(preferences.getString(Provider.CA_CERT, "")); + provider.setVpnCertificate(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); + provider.setPrivateKey(preferences.getString(PROVIDER_PRIVATE_KEY, "")); + provider.setEipServiceJson(new JSONObject(preferences.getString(PROVIDER_EIP_DEFINITION, ""))); + } catch (MalformedURLException | JSONException e) { + e.printStackTrace(); + } + + return provider; + } + + public static String getFromPersistedProvider(String toFetch, String providerDomain, SharedPreferences preferences) { + return preferences.getString(toFetch + "." + providerDomain, ""); + } + + public static String getProviderName(String provider) { + return getProviderName(null, provider); + } + + public static String getProviderName(@Nullable SharedPreferences preferences) { + return getProviderName(preferences,null); + } + + public static String getProviderName(@Nullable SharedPreferences preferences, @Nullable String provider) { + if (provider == null && preferences != null) { + provider = preferences.getString(Provider.KEY, ""); + } + try { + JSONObject providerJson = new JSONObject(provider); + String lang = Locale.getDefault().getLanguage(); + return providerJson.getJSONObject(Provider.NAME).getString(lang); + } catch (JSONException e) { + try { + JSONObject providerJson = new JSONObject(provider); + return providerJson.getJSONObject(Provider.NAME).getString("en"); + } catch (JSONException e2) { + return null; + } + } catch (NullPointerException npe) { + return null; + } + } + + public static String getProviderDomain(SharedPreferences preferences) { + return getProviderDomain(preferences, null); + } + + public static String getProviderDomain(String provider) { + return getProviderDomain(null, provider); + } + + public static String getProviderDomain(@Nullable SharedPreferences preferences, @Nullable String provider) { + if (provider == null && preferences != null) { + provider = preferences.getString(Provider.KEY, ""); + } + try { + JSONObject providerJson = new JSONObject(provider); + return providerJson.getString(Provider.DOMAIN); + } catch (JSONException | NullPointerException e) { + return null; + } + } + + public static String getDescription(SharedPreferences preferences) { + try { + JSONObject providerJson = new JSONObject(preferences.getString(Provider.KEY, "")); + String lang = Locale.getDefault().getLanguage(); + return providerJson.getJSONObject(Provider.DESCRIPTION).getString(lang); + } catch (JSONException e) { + try { + JSONObject providerJson = new JSONObject(preferences.getString(Provider.KEY, "")); + return providerJson.getJSONObject(Provider.DESCRIPTION).getString("en"); + } catch (JSONException e1) { + return null; + } + } + } + + // TODO: replace commit with apply after refactoring EIP + //FIXME: don't save private keys in shared preferences! use the keystore + public static void storeProviderInPreferences(SharedPreferences preferences, Provider provider) { + preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). + putString(Provider.MAIN_URL, provider.getMainUrlString()). + putString(Provider.KEY, provider.getDefinitionString()). + putString(Provider.CA_CERT, provider.getCaCert()). + putString(PROVIDER_EIP_DEFINITION, provider.getEipServiceJsonString()). + putString(PROVIDER_PRIVATE_KEY, provider.getPrivateKey()). + putString(PROVIDER_VPN_CERTIFICATE, provider.getVpnCertificate()). + commit(); + + String providerDomain = provider.getDomain(); + preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). + putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrlString()). + putString(Provider.KEY + "." + providerDomain, provider.getDefinitionString()). + putString(Provider.CA_CERT + "." + providerDomain, provider.getCaCert()). + putString(PROVIDER_EIP_DEFINITION + "." + providerDomain, provider.getEipServiceJsonString()). + apply(); + } + + + public static void clearDataOfLastProvider(SharedPreferences preferences) { + clearDataOfLastProvider(preferences, false); + } + + @Deprecated + public static void clearDataOfLastProvider(SharedPreferences preferences, boolean commit) { + Map allEntries = preferences.getAll(); + List lastProvidersKeys = new ArrayList<>(); + for (Map.Entry entry : allEntries.entrySet()) { + //sort out all preferences that don't belong to the last provider + if (entry.getKey().startsWith(Provider.KEY + ".") || + entry.getKey().startsWith(Provider.CA_CERT + ".") || + entry.getKey().startsWith(Provider.CA_CERT_FINGERPRINT + "." )|| + entry.getKey().equals(PREFERENCES_APP_VERSION) + ) { + continue; + } + lastProvidersKeys.add(entry.getKey()); + } + + SharedPreferences.Editor preferenceEditor = preferences.edit(); + for (String key : lastProvidersKeys) { + preferenceEditor.remove(key); + } + if (commit) { + preferenceEditor.commit(); + } else { + preferenceEditor.apply(); + } + } + + public static void deleteProviderDetailsFromPreferences(@NonNull SharedPreferences preferences, String providerDomain) { + preferences.edit(). + remove(Provider.KEY + "." + providerDomain). + remove(Provider.CA_CERT + "." + providerDomain). + remove(Provider.CA_CERT_FINGERPRINT + "." + providerDomain). + remove(Provider.MAIN_URL + "." + providerDomain). + remove(Provider.KEY + "." + providerDomain). + remove(Provider.CA_CERT + "." + providerDomain). + remove(PROVIDER_EIP_DEFINITION + "." + providerDomain). + remove(PROVIDER_PRIVATE_KEY + "." + providerDomain). + remove(PROVIDER_VPN_CERTIFICATE + "." + providerDomain). + apply(); + } + + public static void saveBattery(Context context, boolean isEnabled) { + if (context == null) { + return; + } + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + preferences.edit().putBoolean(DEFAULT_SHARED_PREFS_BATTERY_SAVER, isEnabled).apply(); + } + + public static boolean getSaveBattery(Context context) { + if (context == null) { + return false; + } + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + return preferences.getBoolean(DEFAULT_SHARED_PREFS_BATTERY_SAVER, false); + } + + public static void saveShowAlwaysOnDialog(Context context, boolean showAlwaysOnDialog) { + if (context == null) { + return; + } + SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); + preferences.edit().putBoolean(ALWAYS_ON_SHOW_DIALOG, showAlwaysOnDialog).apply(); + } + + public static boolean getShowAlwaysOnDialog(Context context) { + if (context == null) { + return true; + } + SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); + return preferences.getBoolean(ALWAYS_ON_SHOW_DIALOG, true); + } +} -- cgit v1.2.3 From 30d2a98bb521e881ddb60ebfd9e3f48842ecf4b7 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sun, 18 Mar 2018 03:46:26 +0100 Subject: #8885 tests for ProviderManager and fixed behavior for deleted providers --- .../se/leap/bitmaskclient/ProviderManager.java | 47 +++++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) (limited to 'app/src/main/java/se/leap') diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java index 97ba3b98..a36c2dec 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java @@ -1,6 +1,7 @@ package se.leap.bitmaskclient; import android.content.res.AssetManager; +import android.support.annotation.VisibleForTesting; import com.pedrogomez.renderers.AdapteeCollection; @@ -8,9 +9,7 @@ import org.json.JSONException; import org.json.JSONObject; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; @@ -22,6 +21,11 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import static se.leap.bitmaskclient.utils.FileHelper.createFile; +import static se.leap.bitmaskclient.utils.FileHelper.persistFile; +import static se.leap.bitmaskclient.utils.InputStreamHelper.getInputStreamFrom; +import static se.leap.bitmaskclient.utils.InputStreamHelper.loadInputStreamAsString; + /** * Created by parmegv on 4/12/14. */ @@ -37,6 +41,8 @@ public class ProviderManager implements AdapteeCollection { private static ProviderManager instance; final private static String URLS = "urls"; + final private static String EXT_JSON = ".json"; + final private static String EXT_PEM = ".pem"; public static ProviderManager getInstance(AssetManager assetsManager, File externalFilesDir) { if (instance == null) @@ -45,6 +51,11 @@ public class ProviderManager implements AdapteeCollection { return instance; } + @VisibleForTesting + static void reset() { + instance = null; + } + private ProviderManager(AssetManager assetManager, File externalFilesDir) { this.assetsManager = assetManager; addDefaultProviders(assetManager); @@ -79,8 +90,8 @@ public class ProviderManager implements AdapteeCollection { String provider = file.substring(0, file.length() - ".url".length()); InputStream provider_file = assetsManager.open(directory + "/" + file); mainUrl = extractMainUrlFromInputStream(provider_file); - certificate = ConfigHelper.loadInputStreamAsString(assetsManager.open(provider + ".pem")); - providerDefinition = ConfigHelper.loadInputStreamAsString(assetsManager.open(provider + ".json")); + certificate = loadInputStreamAsString(assetsManager.open(provider + EXT_PEM)); + providerDefinition = loadInputStreamAsString(assetsManager.open(provider + EXT_JSON)); } catch (IOException e) { e.printStackTrace(); } @@ -107,7 +118,7 @@ public class ProviderManager implements AdapteeCollection { Set providers = new HashSet<>(); try { for (String file : files) { - String mainUrl = extractMainUrlFromInputStream(ConfigHelper.getInputStreamFrom(externalFilesDir.getAbsolutePath() + "/" + file)); + String mainUrl = extractMainUrlFromInputStream(getInputStreamFrom(externalFilesDir.getAbsolutePath() + "/" + file)); providers.add(new Provider(new URL(mainUrl))); } } catch (MalformedURLException | FileNotFoundException e) { @@ -219,19 +230,33 @@ public class ProviderManager implements AdapteeCollection { defaultProviderURLs.clear(); } - //FIXME: removed custom providers should be deleted here as well void saveCustomProvidersToFile() { try { + deleteLegacyCustomProviders(); + for (Provider provider : customProviders) { - File providerFile = new File(externalFilesDir, provider.getName() + ".json"); + File providerFile = createFile(externalFilesDir, provider.getName() + EXT_JSON); if (!providerFile.exists()) { - FileWriter writer = new FileWriter(providerFile); - writer.write(provider.toJson().toString()); - writer.close(); + persistFile(providerFile, provider.toJson().toString()); } } - } catch (IOException e) { + } catch (IOException | SecurityException e) { e.printStackTrace(); } } + + /** + * Deletes persisted custom providers from from internal storage that are not in customProviders list anymore + */ + private void deleteLegacyCustomProviders() throws IOException, SecurityException { + Set persistedCustomProviders = externalFilesDir != null && externalFilesDir.isDirectory() ? + providersFromFiles(externalFilesDir.list()) : new HashSet(); + persistedCustomProviders.removeAll(customProviders); + for (Provider providerToDelete : persistedCustomProviders) { + File providerFile = createFile(externalFilesDir, providerToDelete.getName() + EXT_JSON); + if (providerFile.exists()) { + providerFile.delete(); + } + } + } } -- cgit v1.2.3 From dcf338a665a56d28db8f001b6118d15cfdb88989 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Wed, 21 Mar 2018 23:31:12 +0100 Subject: #8885 rename class variable name --- .../java/se/leap/bitmaskclient/utils/KeyStoreHelper.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'app/src/main/java/se/leap') diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java index 0cc9687b..48d4cbad 100644 --- a/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java @@ -2,7 +2,6 @@ package se.leap.bitmaskclient.utils; import java.io.IOException; import java.io.InputStream; -import java.math.BigInteger; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -15,7 +14,7 @@ import java.security.cert.X509Certificate; */ public class KeyStoreHelper { - private static KeyStore keystore_trusted; + private static KeyStore trustedKeystore; /** * Adds a new X509 certificate given its input stream and its provider name @@ -29,7 +28,7 @@ public class KeyStoreHelper { cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); - keystore_trusted.setCertificateEntry(provider, cert); + trustedKeystore.setCertificateEntry(provider, cert); } catch (CertificateException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -49,11 +48,11 @@ public class KeyStoreHelper { try { X509Certificate cert = ConfigHelper.parseX509CertificateFromString(certificate); - if (keystore_trusted == null) { - keystore_trusted = KeyStore.getInstance("BKS"); - keystore_trusted.load(null); + if (trustedKeystore == null) { + trustedKeystore = KeyStore.getInstance("BKS"); + trustedKeystore.load(null); } - keystore_trusted.setCertificateEntry(provider, cert); + trustedKeystore.setCertificateEntry(provider, cert); } catch (KeyStoreException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -73,7 +72,7 @@ public class KeyStoreHelper { * @return class wide keystore */ public static KeyStore getKeystore() { - return keystore_trusted; + return trustedKeystore; } } -- cgit v1.2.3 From b8e562d03695f5a73809feb0751a9c296c9c5fa4 Mon Sep 17 00:00:00 2001 From: Janak Amarasena Date: Sat, 23 Jun 2018 16:25:38 +0530 Subject: Modified donation reminder --- .../main/java/se/leap/bitmaskclient/Constants.java | 1 + .../java/se/leap/bitmaskclient/EipFragment.java | 38 +++++++++++++--------- .../se/leap/bitmaskclient/utils/DateHelper.java | 30 +++++++++++++++++ 3 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java (limited to 'app/src/main/java/se/leap') diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java index af1d55ec..8ec3fcc7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java @@ -99,6 +99,7 @@ public interface Constants { String DONATION_URL = TextUtils.isEmpty(BuildConfig.donation_url) ? BuildConfig.default_donation_url : BuildConfig.donation_url; String LAST_DONATION_REMINDER_DATE = "last_donation_reminder_date"; + String FIRST_TIME_USER_DATE = "first_time_user_date"; } diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java index 535322e5..51f787b7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java @@ -58,6 +58,7 @@ import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.ProfileManager; import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.eip.EipStatus; +import se.leap.bitmaskclient.utils.DateHelper; import se.leap.bitmaskclient.views.VpnStateImage; import static android.view.View.GONE; @@ -65,6 +66,7 @@ import static android.view.View.VISIBLE; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; import static se.leap.bitmaskclient.Constants.DONATION_REMINDER_DURATION; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; +import static se.leap.bitmaskclient.Constants.FIRST_TIME_USER_DATE; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_LOG_IN; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; @@ -530,7 +532,8 @@ public class EipFragment extends Fragment implements Observer { }).setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { - saveLastDonationReminderDate(); + preferences.edit().putString(LAST_DONATION_REMINDER_DATE, + DateHelper.getCurrentDateString()).apply(); } }).show(); } @@ -545,29 +548,32 @@ public class EipFragment extends Fragment implements Observer { return false; } - String lastDonationReminderDate = preferences.getString(LAST_DONATION_REMINDER_DATE, null); - if (lastDonationReminderDate == null) { - return true; + String firstTimeUserDate = preferences.getString(FIRST_TIME_USER_DATE, null); + if (firstTimeUserDate == null) { + preferences.edit().putString(FIRST_TIME_USER_DATE, DateHelper.getCurrentDateString()).apply(); + return false; } - SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US); - Date lastDate; try { - lastDate = sdf.parse(lastDonationReminderDate); + long diffDays; + + diffDays = DateHelper.getDateDiffToCurrentDateInDays(firstTimeUserDate); + if (diffDays < 1) { + return false; + } + + String lastDonationReminderDate = preferences.getString(LAST_DONATION_REMINDER_DATE, null); + if (lastDonationReminderDate == null) { + return true; + } + diffDays = DateHelper.getDateDiffToCurrentDateInDays(lastDonationReminderDate); + return diffDays >= DONATION_REMINDER_DURATION; + } catch (ParseException e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); return false; } - - Date currentDate = new Date(); - long diffDays = (currentDate.getTime() - lastDate.getTime()) / ONE_DAY; - return diffDays >= DONATION_REMINDER_DURATION; } - private void saveLastDonationReminderDate() { - SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US); - Date lastDate = new Date(); - preferences.edit().putString(LAST_DONATION_REMINDER_DATE, sdf.format(lastDate)).apply(); - } } diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java new file mode 100644 index 00000000..a8649132 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java @@ -0,0 +1,30 @@ +package se.leap.bitmaskclient.utils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * Contains helper methods related to date manipulation. + * + * @author Janak + */ +public class DateHelper { + private static final String DATE_PATTERN = "dd/MM/yyyy"; + private static final int ONE_DAY = 86400000; //1000*60*60*24 + + public static long getDateDiffToCurrentDateInDays(String startDate) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US); + Date lastDate; + lastDate = sdf.parse(startDate); + Date currentDate = new Date(); + return (currentDate.getTime() - lastDate.getTime()) / ONE_DAY; + } + + public static String getCurrentDateString() { + SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US); + Date lastDate = new Date(); + return sdf.format(lastDate); + } +} -- cgit v1.2.3 From 9cbcd3bad1f0aa33b7c08dc18f7065a126af7eb5 Mon Sep 17 00:00:00 2001 From: Janak Amarasena Date: Tue, 26 Jun 2018 22:53:43 +0530 Subject: Minor update according to received feedback --- app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'app/src/main/java/se/leap') diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java index a8649132..523c8c4c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java @@ -16,8 +16,7 @@ public class DateHelper { public static long getDateDiffToCurrentDateInDays(String startDate) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US); - Date lastDate; - lastDate = sdf.parse(startDate); + Date lastDate = sdf.parse(startDate); Date currentDate = new Date(); return (currentDate.getTime() - lastDate.getTime()) / ONE_DAY; } -- cgit v1.2.3