diff options
Diffstat (limited to 'app/src')
48 files changed, 982 insertions, 313 deletions
| diff --git a/app/src/custom/res/drawable/ic_colorsquare.xml b/app/src/custom/res/drawable/ic_colorsquare.xml new file mode 100644 index 00000000..4b60e9dc --- /dev/null +++ b/app/src/custom/res/drawable/ic_colorsquare.xml @@ -0,0 +1,52 @@ +<vector android:height="24dp" android:viewportHeight="100.0" +    android:viewportWidth="100.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> +    <path android:pathData="M50,50m-82000,0a82000,82000 0,1 1,164000 0a82000,82000 0,1 1,-164000 0"/> +    <path android:fillAlpha="1" android:fillColor="#e6ee9c" +        android:pathData="M50,50 L30664.67,-73860.37A80000,80000 0,0 0,50 -79950Z" +        android:strokeColor="#e6ee9c" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#fff59d" +        android:pathData="m50,50 l56568.54,-56568.54a80000,80000 0,0 0,-25953.87 -17341.82z" +        android:strokeColor="#fff59d" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#ffe082" +        android:pathData="m50,50 l73910.37,-30614.67a80000,80000 0,0 0,-17341.82 -25953.87z" +        android:strokeColor="#ffe082" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#ffcc80" +        android:pathData="M50,50L80050,50A80000,80000 0,0 0,73960.37 -30564.67Z" +        android:strokeColor="#ffcc80" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#ffab91" +        android:pathData="M50,50 L73960.37,30664.67A80000,80000 0,0 0,80050 50Z" +        android:strokeColor="#ffab91" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#ef9a9a" +        android:pathData="m50,50 l56568.54,56568.54a80000,80000 0,0 0,17341.82 -25953.87z" +        android:strokeColor="#ef9a9a" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#f48fb1" +        android:pathData="m50,50 l30614.67,73910.37a80000,80000 0,0 0,25953.87 -17341.82z" +        android:strokeColor="#f48fb1" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#ce93d8" +        android:pathData="M50,50L50,80050A80000,80000 0,0 0,30664.67 73960.37Z" +        android:strokeColor="#ce93d8" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#b39ddb" +        android:pathData="M50,50 L-30564.67,73960.37A80000,80000 0,0 0,50 80050Z" +        android:strokeColor="#b39ddb" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#9fa8da" +        android:pathData="m50,50 l-56568.54,56568.54a80000,80000 0,0 0,25953.87 17341.82z" +        android:strokeColor="#9fa8da" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#90caf9" +        android:pathData="m50,50 l-73910.37,30614.67a80000,80000 0,0 0,17341.82 25953.87z" +        android:strokeColor="#90caf9" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#81d4fa" +        android:pathData="m50,50l-80000,0a80000,80000 0,0 0,6089.64 30614.67z" +        android:strokeColor="#81d4fa" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#80deea" +        android:pathData="M50,50 L-73860.37,-30564.67A80000,80000 0,0 0,-79950 50Z" +        android:strokeColor="#80deea" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#80cbc4" +        android:pathData="m50,50 l-56568.54,-56568.54a80000,80000 0,0 0,-17341.82 25953.87z" +        android:strokeColor="#80cbc4" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#a5d6a7" +        android:pathData="m50,50 l-30614.67,-73910.37a80000,80000 0,0 0,-25953.87 17341.82z" +        android:strokeColor="#a5d6a7" android:strokeWidth="0"/> +    <path android:fillAlpha="1" android:fillColor="#c5e1a5" +        android:pathData="m50,50l0,-80000a80000,80000 0,0 0,-30614.67 6089.64z" +        android:strokeColor="#c5e1a5" android:strokeWidth="0"/> +</vector> diff --git a/app/src/custom/res/drawable/ic_launcher_background.xml b/app/src/custom/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..d5fccc53 --- /dev/null +++ b/app/src/custom/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" +    android:width="108dp" +    android:height="108dp" +    android:viewportHeight="108" +    android:viewportWidth="108"> +    <path +        android:fillColor="#26A69A" +        android:pathData="M0,0h108v108h-108z" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M9,0L9,108" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,0L19,108" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M29,0L29,108" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M39,0L39,108" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M49,0L49,108" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M59,0L59,108" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M69,0L69,108" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M79,0L79,108" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M89,0L89,108" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M99,0L99,108" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,9L108,9" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,19L108,19" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,29L108,29" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,39L108,39" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,49L108,49" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,59L108,59" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,69L108,69" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,79L108,79" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,89L108,89" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M0,99L108,99" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,29L89,29" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,39L89,39" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,49L89,49" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,59L89,59" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,69L89,69" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M19,79L89,79" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M29,19L29,89" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M39,19L39,89" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M49,19L49,89" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M59,19L59,89" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M69,19L69,89" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +    <path +        android:fillColor="#00000000" +        android:pathData="M79,19L79,89" +        android:strokeColor="#33FFFFFF" +        android:strokeWidth="0.8" /> +</vector> diff --git a/app/src/custom/res/drawable/ic_launcher_foreground.xml b/app/src/custom/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..c7bd21db --- /dev/null +++ b/app/src/custom/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" +    xmlns:aapt="http://schemas.android.com/aapt" +    android:width="108dp" +    android:height="108dp" +    android:viewportHeight="108" +    android:viewportWidth="108"> +    <path +        android:fillType="evenOdd" +        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" +        android:strokeColor="#00000000" +        android:strokeWidth="1"> +        <aapt:attr name="android:fillColor"> +            <gradient +                android:endX="78.5885" +                android:endY="90.9159" +                android:startX="48.7653" +                android:startY="61.0927" +                android:type="linear"> +                <item +                    android:color="#44000000" +                    android:offset="0.0" /> +                <item +                    android:color="#00000000" +                    android:offset="1.0" /> +            </gradient> +        </aapt:attr> +    </path> +    <path +        android:fillColor="#FFFFFF" +        android:fillType="nonZero" +        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" +        android:strokeColor="#00000000" +        android:strokeWidth="1" /> +</vector> diff --git a/app/src/custom/res/drawable/splash_page.xml b/app/src/custom/res/drawable/splash_page.xml new file mode 100644 index 00000000..8fe99847 --- /dev/null +++ b/app/src/custom/res/drawable/splash_page.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> + <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + + <!--Rename ic_splash_background to background image name--> + <!--Change gravity to best fit background image to screen--> + <item android:drawable="@drawable/ic_splash_background" +     android:gravity="fill_horizontal|fill_vertical" /> + + <!--If a foreground image is not need remove this <item> tag--> + <item> +  <!--Rename mask to foreground image name--> +  <bitmap +      android:src="@drawable/mask" +      android:gravity="center" /> + </item> + +</layer-list>
\ No newline at end of file diff --git a/app/src/custom/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/custom/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/app/src/custom/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> +    <background android:drawable="@drawable/ic_launcher_background" /> +    <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/app/src/custom/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/custom/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/app/src/custom/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> +    <background android:drawable="@drawable/ic_launcher_background" /> +    <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/app/src/custom/res/mipmap-hdpi/ic_launcher.png b/app/src/custom/res/mipmap-hdpi/ic_launcher.pngBinary files differ new file mode 100644 index 00000000..a2f59082 --- /dev/null +++ b/app/src/custom/res/mipmap-hdpi/ic_launcher.png diff --git a/app/src/custom/res/mipmap-hdpi/ic_launcher_round.png b/app/src/custom/res/mipmap-hdpi/ic_launcher_round.pngBinary files differ new file mode 100644 index 00000000..1b523998 --- /dev/null +++ b/app/src/custom/res/mipmap-hdpi/ic_launcher_round.png diff --git a/app/src/custom/res/mipmap-mdpi/ic_launcher.png b/app/src/custom/res/mipmap-mdpi/ic_launcher.pngBinary files differ new file mode 100644 index 00000000..ff10afd6 --- /dev/null +++ b/app/src/custom/res/mipmap-mdpi/ic_launcher.png diff --git a/app/src/custom/res/mipmap-mdpi/ic_launcher_round.png b/app/src/custom/res/mipmap-mdpi/ic_launcher_round.pngBinary files differ new file mode 100644 index 00000000..115a4c76 --- /dev/null +++ b/app/src/custom/res/mipmap-mdpi/ic_launcher_round.png diff --git a/app/src/custom/res/mipmap-xhdpi/ic_launcher.png b/app/src/custom/res/mipmap-xhdpi/ic_launcher.pngBinary files differ new file mode 100644 index 00000000..dcd3cd80 --- /dev/null +++ b/app/src/custom/res/mipmap-xhdpi/ic_launcher.png diff --git a/app/src/custom/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/custom/res/mipmap-xhdpi/ic_launcher_round.pngBinary files differ new file mode 100644 index 00000000..459ca609 --- /dev/null +++ b/app/src/custom/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/app/src/custom/res/mipmap-xxhdpi/ic_launcher.png b/app/src/custom/res/mipmap-xxhdpi/ic_launcher.pngBinary files differ new file mode 100644 index 00000000..8ca12fe0 --- /dev/null +++ b/app/src/custom/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app/src/custom/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/custom/res/mipmap-xxhdpi/ic_launcher_round.pngBinary files differ new file mode 100644 index 00000000..8e19b410 --- /dev/null +++ b/app/src/custom/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/app/src/custom/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/custom/res/mipmap-xxxhdpi/ic_launcher.pngBinary files differ new file mode 100644 index 00000000..b824ebdd --- /dev/null +++ b/app/src/custom/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app/src/custom/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/custom/res/mipmap-xxxhdpi/ic_launcher_round.pngBinary files differ new file mode 100644 index 00000000..4c19a13c --- /dev/null +++ b/app/src/custom/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/app/src/custom/res/values/custom-theme.xml b/app/src/custom/res/values/custom-theme.xml new file mode 100644 index 00000000..4421adb9 --- /dev/null +++ b/app/src/custom/res/values/custom-theme.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> +    <!--Colors--> +    <!--Color of the action bar--> +    <color name="colorPrimary">#b39ddb</color> +    <!--Color of the status bar--> +    <color name="colorPrimaryDark">#ac97d2</color> +    <!--Font color of the action bar title--> +    <color name="colorActionBarTitleFont">#ffffff</color> +    <!--Font color of the action bar subtitle--> +    <color name="colorActionBarSubtitleFont">#000000</color> +</resources> diff --git a/app/src/custom/res/values/strings.xml b/app/src/custom/res/values/strings.xml new file mode 100644 index 00000000..b923b42b --- /dev/null +++ b/app/src/custom/res/values/strings.xml @@ -0,0 +1,4 @@ +<?xml version='1.0' encoding='UTF-8'?> +<resources> +  <string name="donate_message">Please donate today if you value secure communication that is easy for both the end-user and the service provider.</string> +</resources> diff --git a/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java b/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java index 61ae790b..e3fb549f 100644 --- a/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java +++ b/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java @@ -46,6 +46,7 @@ import javax.net.ssl.X509TrustManager;  import okhttp3.OkHttpClient;  import se.leap.bitmaskclient.eip.EIP; +import se.leap.bitmaskclient.utils.ConfigHelper;  import static android.text.TextUtils.isEmpty;  import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 84fb83f8..b1131850 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -86,7 +86,7 @@          <activity              android:name=".MainActivity" -            android:label="@string/title_activity_main" +            android:label="@string/app_name"              android:launchMode="singleTop" />          <activity diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java index 7fa09441..2efc2c1f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java @@ -100,6 +100,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/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 868d2876..c44e8a3e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java @@ -38,6 +38,7 @@ import org.json.JSONObject;  import se.leap.bitmaskclient.drawer.NavigationDrawerFragment;  import se.leap.bitmaskclient.eip.EipCommand;  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; @@ -61,6 +62,8 @@ import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_  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 { @@ -92,7 +95,7 @@ public class MainActivity extends AppCompatActivity {                  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( @@ -179,7 +182,7 @@ public class MainActivity extends AppCompatActivity {                  return;              } -            ConfigHelper.storeProviderInPreferences(preferences, provider); +            storeProviderInPreferences(preferences, provider);              navigationDrawerFragment.refresh();              switch (requestCode) { @@ -296,7 +299,7 @@ public class MainActivity extends AppCompatActivity {              case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE:                  provider = resultData.getParcelable(PROVIDER_KEY); -                ConfigHelper.storeProviderInPreferences(preferences, provider); +                storeProviderInPreferences(preferences, provider);                  EipCommand.startVPN(this, true);                  break;              case INCORRECTLY_UPDATED_INVALID_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 753172e6..8f3acf1d 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; @@ -106,6 +107,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 @@ -229,7 +233,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) { @@ -765,16 +769,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(); @@ -860,7 +864,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/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<Provider> {      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<Provider> {          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<Provider> {                      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<Provider> {          Set<Provider> 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<Provider> {          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<Provider> persistedCustomProviders = externalFilesDir != null && externalFilesDir.isDirectory() ? +                providersFromFiles(externalFilesDir.list()) : new HashSet<Provider>(); +            persistedCustomProviders.removeAll(customProviders); +        for (Provider providerToDelete : persistedCustomProviders) { +            File providerFile = createFile(externalFilesDir, providerToDelete.getName() + EXT_JSON); +            if (providerFile.exists()) { +                providerFile.delete(); +            } +        } +    }  } 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 be4bdf99..6e9879dd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -50,7 +50,7 @@ import android.widget.ArrayAdapter;  import android.widget.CompoundButton;  import android.widget.ListView; -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.EipFragment; @@ -64,8 +64,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; @@ -82,6 +80,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;  import static se.leap.bitmaskclient.R.string.donate_title;  /** @@ -383,7 +386,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() { @@ -467,14 +470,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; @@ -500,7 +503,7 @@ public class NavigationDrawerFragment extends Fragment {              fragment = new EipFragment();              fragmentTag = EipFragment.TAG;              Bundle arguments = new Bundle(); -            Provider currentProvider = ConfigHelper.getSavedProviderFromSharedPreferences(preferences); +            Provider currentProvider = getSavedProviderFromSharedPreferences(preferences);              arguments.putParcelable(PROVIDER_KEY, currentProvider);              fragment.setArguments(arguments);          } else { @@ -562,7 +565,7 @@ public class NavigationDrawerFragment extends Fragment {      private void refreshAccountListAdapter() {          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/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index 159bc9a7..971d973f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -53,7 +53,6 @@ import se.leap.bitmaskclient.OnBootReceiver;  import static android.app.Activity.RESULT_CANCELED;  import static android.app.Activity.RESULT_OK;  import static android.content.Intent.CATEGORY_DEFAULT; -import static se.leap.bitmaskclient.ConfigHelper.ensureNotOnMainThread;  import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT;  import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE;  import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; @@ -71,6 +70,7 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;  import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;  import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.ERROR_INVALID_VPN_CERTIFICATE;  import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; +import static se.leap.bitmaskclient.utils.ConfigHelper.ensureNotOnMainThread;  /**   * EIP is the abstract base class for interacting with and managing the Encrypted diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index a04ede08..5b4db5af 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -35,8 +35,8 @@ import java.util.List;  import de.blinkt.openvpn.VpnProfile;  import de.blinkt.openvpn.core.Connection;  import de.blinkt.openvpn.core.ProfileManager; -import se.leap.bitmaskclient.ConfigHelper;  import se.leap.bitmaskclient.Provider; +import se.leap.bitmaskclient.utils.PreferenceHelper;  import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY;  import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; @@ -216,7 +216,7 @@ public class GatewaysManager {          // to add all gateways from prefs without duplicates, but this should be refactored.          clearGatewaysAndProfiles();          fromEipServiceJson( -                ConfigHelper.getEipDefinitionFromPreferences(preferences) +                PreferenceHelper.getEipDefinitionFromPreferences(preferences)          );      }  } 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..5bb637b7 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java @@ -0,0 +1,170 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + */ +package se.leap.bitmaskclient.utils; + +import android.content.Context; +import android.os.Looper; +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); +    } + +    public static void ensureNotOnMainThread(@NonNull Context context) throws IllegalStateException{ +        Looper looper = Looper.myLooper(); +        if (looper != null && looper == context.getMainLooper()) { +            throw new IllegalStateException( +                    "calling this from your main thread can lead to deadlock"); +        } +    } + +} 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..523c8c4c --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java @@ -0,0 +1,29 @@ +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 = 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); +    } +} 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..48d4cbad --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java @@ -0,0 +1,78 @@ +package se.leap.bitmaskclient.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/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java index bfc77261..12015dfb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java @@ -1,20 +1,4 @@ -/** - * 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 <http://www.gnu.org/licenses/>. - */ -package se.leap.bitmaskclient; +package se.leap.bitmaskclient.utils;  import android.content.Context;  import android.content.SharedPreferences; @@ -25,7 +9,6 @@ import android.support.annotation.Nullable;  import org.json.JSONException;  import org.json.JSONObject; -import org.spongycastle.util.encoders.Base64;  import java.io.ByteArrayInputStream;  import java.io.FileInputStream; @@ -35,24 +18,13 @@ import java.io.InputStream;  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.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; @@ -63,195 +35,10 @@ 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 + * Created by cyberta on 18.03.18.   */ -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 class PreferenceHelper {      public static boolean providerInSharedPreferences(@NonNull SharedPreferences preferences) {          return preferences.getBoolean(PROVIDER_CONFIGURED, false);      } @@ -452,11 +239,4 @@ public class ConfigHelper {          return result;      } -    public static void ensureNotOnMainThread(@NonNull Context context) throws IllegalStateException{ -        Looper looper = Looper.myLooper(); -        if (looper != null && looper == context.getMainLooper()) { -            throw new IllegalStateException( -                    "calling this from your main thread can lead to deadlock"); -        } -    }  } diff --git a/app/src/main/res/layout/a_main.xml b/app/src/main/res/layout/a_main.xml index bed05d18..21fdaa66 100644 --- a/app/src/main/res/layout/a_main.xml +++ b/app/src/main/res/layout/a_main.xml @@ -20,7 +20,8 @@              android:minHeight="?attr/actionBarSize"              android:layout_width="match_parent"              android:layout_height="wrap_content" -            app:titleTextColor="@android:color/white" +            app:titleTextColor="@color/colorActionBarTitleFont" +            app:subtitleTextColor="@color/colorActionBarSubtitleFont"              android:background="?attr/colorPrimary">          </android.support.v7.widget.Toolbar> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 53ead009..40ab06c5 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -27,4 +27,7 @@      <color name="white">#ffffff</color> +    <color name="colorActionBarTitleFont">@color/white</color> +    <color name="colorActionBarSubtitleFont">@color/black800</color> +  </resources> diff --git a/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java index 885d69db..b1afa6d3 100644 --- a/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java +++ b/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java @@ -31,6 +31,7 @@ import java.util.List;  import okhttp3.OkHttpClient;  import se.leap.bitmaskclient.eip.EIP; +import se.leap.bitmaskclient.utils.ConfigHelper;  import static android.text.TextUtils.isEmpty;  import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; diff --git a/app/src/test/java/se/leap/bitmaskclient/ConfigHelperTest.java b/app/src/test/java/se/leap/bitmaskclient/PreferenceHelperTest.java index 2c7848bc..d49fa08c 100644 --- a/app/src/test/java/se/leap/bitmaskclient/ConfigHelperTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/PreferenceHelperTest.java @@ -6,6 +6,8 @@ import org.junit.Before;  import org.junit.Test;  import se.leap.bitmaskclient.testutils.MockSharedPreferences; +import se.leap.bitmaskclient.utils.ConfigHelper; +import se.leap.bitmaskclient.utils.PreferenceHelper;  import static org.junit.Assert.assertFalse;  import static org.junit.Assert.assertTrue; @@ -14,11 +16,13 @@ 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.testutils.TestSetupHelper.getInputAsString; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; +import static se.leap.bitmaskclient.utils.PreferenceHelper.providerInSharedPreferences;  /**   * Created by cyberta on 17.01.18.   */ -public class ConfigHelperTest { +public class PreferenceHelperTest {      private SharedPreferences mockPreferences; @@ -30,18 +34,18 @@ public class ConfigHelperTest {      @Test      public void providerInSharedPreferences_notInPreferences_returnsFalse() throws Exception { -        assertFalse(ConfigHelper.providerInSharedPreferences(mockPreferences)); +        assertFalse(providerInSharedPreferences(mockPreferences));      }      @Test      public void providerInSharedPreferences_inPreferences_returnsTrue() throws Exception {          mockPreferences.edit().putBoolean(PROVIDER_CONFIGURED, true).apply(); -        assertTrue(ConfigHelper.providerInSharedPreferences(mockPreferences)); +        assertTrue(providerInSharedPreferences(mockPreferences));      }      @Test      public void getSavedProviderFromSharedPreferences_notInPreferences_returnsDefaultProvider() throws Exception { -        Provider provider = ConfigHelper.getSavedProviderFromSharedPreferences(mockPreferences); +        Provider provider = getSavedProviderFromSharedPreferences(mockPreferences);          assertFalse(provider.isConfigured());      } @@ -55,7 +59,7 @@ public class ConfigHelperTest {                  .putString(PROVIDER_VPN_CERTIFICATE, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.vpn_cert.pem")))                  .putString(PROVIDER_PRIVATE_KEY, getInputAsString(getClass().getClassLoader().getResourceAsStream("private_rsa_key.pem")))                  .apply(); -        Provider provider = ConfigHelper.getSavedProviderFromSharedPreferences(mockPreferences); +        Provider provider = getSavedProviderFromSharedPreferences(mockPreferences);          assertTrue(provider.isConfigured());      } diff --git a/app/src/test/java/se/leap/bitmaskclient/ProviderManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/ProviderManagerTest.java new file mode 100644 index 00000000..1914f989 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/ProviderManagerTest.java @@ -0,0 +1,189 @@ +package se.leap.bitmaskclient; + +import android.content.res.AssetManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; + +import se.leap.bitmaskclient.utils.ConfigHelper; +import se.leap.bitmaskclient.utils.FileHelper; +import se.leap.bitmaskclient.utils.InputStreamHelper; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; +import static se.leap.bitmaskclient.testutils.MockHelper.mockFileHelper; +import static se.leap.bitmaskclient.testutils.MockHelper.mockInputStreamHelper; + +/** + * Created by cyberta on 20.02.18. + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ConfigHelper.class, FileHelper.class, InputStreamHelper.class}) +public class ProviderManagerTest { + +    @Mock +    private AssetManager assetManager; +    @Mock +    private File file; +    private ProviderManager providerManager; + +    @Before +    public void setup() throws Exception { +        //mock assetManager methods +        //-------------------------- +        when(assetManager.open(anyString())).thenAnswer(new Answer<InputStream>() { +            @Override +            public InputStream answer(InvocationOnMock invocation) throws Throwable { +                String filename = "preconfigured/" + invocation.getArguments()[0]; +                return getClass().getClassLoader().getResourceAsStream(filename); +            } +        }); +        when(assetManager.list(anyString())).thenAnswer(new Answer<String[]>() { +            @Override +            public String[] answer(InvocationOnMock invocation) throws Throwable { +                String path = (String) invocation.getArguments()[0]; +                if ("urls".equals(path)) { +                    String[] preconfiguredUrls = new String[3]; +                    preconfiguredUrls[0] = "calyx.net.url"; +                    preconfiguredUrls[1] = "demo.bitmask.net.url"; +                    preconfiguredUrls[2] = "riseup.net.url"; +                    return preconfiguredUrls; +                } else +                    throw new IllegalArgumentException("You need to implement the expected path manually!"); +            } +        }); + +        //mock File methods +        //------------------ +        when(file.isDirectory()).thenReturn(true); + +        ArrayList<String> mockedCustomProviderList = new ArrayList<>(); +        mockedCustomProviderList.add("leapcolombia.json"); +        String[] mockedCustomProviderArray = new String[mockedCustomProviderList.size()]; +        mockedCustomProviderArray = mockedCustomProviderList.toArray(mockedCustomProviderArray); +        when(file.list()).thenReturn(mockedCustomProviderArray); + +        when(file.getAbsolutePath()).thenReturn("externalDir"); +        when(file.getPath()).thenReturn("externalDir"); +        mockFileHelper(file); + +        // mock inputStream +        //----------------------------------- +        mockInputStreamHelper(); + +    } + +    @After +    public void tearDown() { +        ProviderManager.reset(); +    } + +    @Test +    public void testSize_has5ProvidersWithCurrentTestSetup() { +        providerManager = ProviderManager.getInstance(assetManager, file); +        assertEquals("3 preconfigured, 1 custom provider, 1 dummy provider", 5, providerManager.size()); +    } + +    @Test +    public void testAdd_newCustomProviderThatIsNotPartOfDefaultNorCustomList_returnTrue() throws Exception { +        providerManager = ProviderManager.getInstance(assetManager, file); +        Provider customProvider = new Provider("https://anewprovider.org"); +        assertTrue("custom provider added: ", providerManager.add(customProvider)); +        assertEquals("3 preconfigured, 2 custom providers, 1 dummy provider", 6, providerManager.providers().size()); +    } + +    @Test +    public void testAdd_newCustomProviderThatIsNotPartOfDefaultButOfCustomList_returnFalse() throws Exception { +        providerManager = ProviderManager.getInstance(assetManager, file); +        Provider customProvider = new Provider("https://leapcolombia.org"); +        assertFalse("custom provider added: ", providerManager.add(customProvider)); +        assertEquals("3 preconfigured, 1 custom provider, 1 dummy provider", 5, providerManager.providers().size()); +    } + +    @Test +    public void testAdd_newCustomProviderThatIsPartOfDefaultButNotOfCustomList_returnFalse() throws Exception { +        providerManager = ProviderManager.getInstance(assetManager, file); +        Provider customProvider = new Provider("https://demo.bitmask.net"); +        assertFalse("custom provider added: ", providerManager.add(customProvider)); +        assertEquals("3 preconfigured, 1 custom provider, 1 dummy provider", 5, providerManager.providers().size()); +    } + +    @Test +    public void testRemove_ProviderIsPartOfDefaultButNotCustomList_returnsFalse() throws Exception { +        providerManager = ProviderManager.getInstance(assetManager, file); +        Provider customProvider = new Provider("https://demo.bitmask.net"); +        assertFalse("custom provider not removed: ", providerManager.remove(customProvider)); +        assertEquals("3 preconfigured, 1 custom provider, 1 dummy provider", 5, providerManager.providers().size()); +    } + +    @Test +    public void testRemove_ProviderIsNotPartOfDefaultButOfCustomList_returnsTrue() throws Exception { +        providerManager = ProviderManager.getInstance(assetManager, file); +        Provider customProvider = new Provider("https://leapcolombia.org"); +        assertTrue("custom provider not removed: ", providerManager.remove(customProvider)); +        assertEquals("3 preconfigured, 0 custom providers, 1 dummy provider", 4, providerManager.providers().size()); +    } + +    @Test +    public void testRemove_ProviderIsNotPartOfDefaultNorOfCustomList_returnsFalse() throws Exception { +        providerManager = ProviderManager.getInstance(assetManager, file); +        Provider customProvider = new Provider("https://anotherprovider.org"); +        assertFalse("custom provider not removed: ", providerManager.remove(customProvider)); +        assertEquals("3 preconfigured, 1 custom providers, 1 dummy provider", 5, providerManager.providers().size()); +    } + +    @Test +    public void testClear_ProvidersListHasOnlyDummyProvider() throws Exception { +        providerManager = ProviderManager.getInstance(assetManager, file); +        providerManager.clear(); +        assertEquals("1 providers", 1, providerManager.providers().size()); +        assertEquals("provider is dummy element", "https://example.net", providerManager.get(0).getMainUrlString()); +    } + +    @Test +    public void testSaveCustomProvidersToFile_CustomProviderDeleted_deletesFromDir() throws Exception { +        when(file.exists()).thenReturn(true); +        providerManager = ProviderManager.getInstance(assetManager, file); +        //leapcolombia is mocked custom provider from setup +        Provider customProvider = new Provider("https://leapcolombia.org"); +        providerManager.remove(customProvider); +        providerManager.saveCustomProvidersToFile(); +        verify(file, times(1)).delete(); +    } + + +    @Test +    public void testSaveCustomProvidersToFile_newCustomProviders_persistNew() throws Exception { +        when(file.list()).thenReturn(new String[0]); +        when(file.exists()).thenReturn(false); +        providerManager = ProviderManager.getInstance(assetManager, file); +        Provider customProvider = new Provider("https://anotherprovider.org"); +        Provider secondCustomProvider = new Provider("https://yetanotherprovider.org"); +        providerManager.add(customProvider); +        providerManager.add(secondCustomProvider); +        providerManager.saveCustomProvidersToFile(); + +        verifyStatic(FileHelper.class, times(2)); +        FileHelper.persistFile(any(File.class), anyString()); +    } + + +}
\ No newline at end of file diff --git a/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java b/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java index 495d5b3f..a141edec 100644 --- a/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java @@ -1,9 +1,7 @@  package se.leap.bitmaskclient; -import org.json.JSONException;  import org.junit.Test; -import java.io.IOException;  import java.util.HashSet;  import java.util.Set; 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 4842d170..7283968b 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java @@ -38,13 +38,15 @@ import java.io.IOException;  import java.security.NoSuchAlgorithmException;  import java.security.cert.CertificateEncodingException; -import se.leap.bitmaskclient.ConfigHelper; +import se.leap.bitmaskclient.testutils.MockHelper; +import se.leap.bitmaskclient.utils.ConfigHelper;  import se.leap.bitmaskclient.Provider;  import se.leap.bitmaskclient.ProviderAPI;  import se.leap.bitmaskclient.ProviderApiConnector;  import se.leap.bitmaskclient.ProviderApiManager;  import se.leap.bitmaskclient.ProviderApiManagerBase;  import se.leap.bitmaskclient.testutils.MockSharedPreferences; +import se.leap.bitmaskclient.utils.PreferenceHelper;  import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY;  import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; @@ -58,6 +60,7 @@ 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.mockIntent; +import static se.leap.bitmaskclient.testutils.MockHelper.mockPreferenceHelper;  import static se.leap.bitmaskclient.testutils.MockHelper.mockProviderApiConnector;  import static se.leap.bitmaskclient.testutils.MockHelper.mockResources;  import static se.leap.bitmaskclient.testutils.MockHelper.mockResultReceiver; @@ -72,7 +75,7 @@ import static se.leap.bitmaskclient.testutils.TestSetupHelper.getProvider;   */  @RunWith(PowerMockRunner.class) -@PrepareForTest({ProviderApiManager.class, TextUtils.class, ConfigHelper.class, ProviderApiConnector.class}) +@PrepareForTest({ProviderApiManager.class, TextUtils.class, ConfigHelper.class, ProviderApiConnector.class, PreferenceHelper.class})  public class ProviderApiManagerTest {      private SharedPreferences mockPreferences; @@ -172,8 +175,8 @@ public class ProviderApiManagerTest {      @Test      public void test_handleIntentSetupProvider_happyPath_storedProviderAndCAFromPreviousSetup() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {          Provider provider = new Provider("https://riseup.net"); -        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494", getConfiguredProvider()); - +        mockPreferenceHelper(getConfiguredProvider()); +        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");          mockProviderApiConnector(NO_ERROR);          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("riseup.net.pem"))).apply(); @@ -238,7 +241,8 @@ public class ProviderApiManagerTest {      @Test      public void test_handleIntentSetupProvider_storedProviderAndCAFromPreviousSetup_failedPinning() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {          Provider provider = new Provider("https://riseup.net"); -        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495", getConfiguredProvider()); +        mockPreferenceHelper(getConfiguredProvider()); +        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495");          mockProviderApiConnector(NO_ERROR);          mockPreferences.edit().putString(Provider.KEY + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))).apply(); @@ -308,8 +312,8 @@ public class ProviderApiManagerTest {      @Test      public void test_handleIntentSetupProvider_preseededProviderAndCA_ValidCertificateButUpdatedCertificateOnServerSide() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {          Provider provider = getConfiguredProvider(); - -        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494", getConfiguredProvider()); +        mockPreferenceHelper(provider); +        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");          mockProviderApiConnector(ERROR_CASE_UPDATED_CERTIFICATE);          providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -331,8 +335,8 @@ public class ProviderApiManagerTest {      @Test      public void test_handleIntentSetupProvider_storedProviderAndCAFromPreviousSetup_ValidCertificateButUpdatedCertificateOnServerSide() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {          Provider provider = new Provider("https://riseup.net"); - -        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494", getConfiguredProvider()); +        mockPreferenceHelper(getConfiguredProvider()); +        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");          mockProviderApiConnector(ERROR_CASE_UPDATED_CERTIFICATE);          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("riseup.net.pem"))).apply(); 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 d85b050f..d68296a8 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java @@ -14,6 +14,7 @@ import org.json.JSONObject;  import org.mockito.invocation.InvocationOnMock;  import org.mockito.stubbing.Answer; +import java.io.File;  import java.io.FileNotFoundException;  import java.io.IOException;  import java.io.InputStream; @@ -29,12 +30,15 @@ import java.util.Map;  import java.util.Set;  import okhttp3.OkHttpClient; -import se.leap.bitmaskclient.ConfigHelper;  import se.leap.bitmaskclient.OkHttpClientGenerator;  import se.leap.bitmaskclient.Provider;  import se.leap.bitmaskclient.R;  import se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider;  import se.leap.bitmaskclient.testutils.matchers.BundleMatcher; +import se.leap.bitmaskclient.utils.ConfigHelper; +import se.leap.bitmaskclient.utils.FileHelper; +import se.leap.bitmaskclient.utils.InputStreamHelper; +import se.leap.bitmaskclient.utils.PreferenceHelper;  import static org.junit.Assert.assertEquals;  import static org.junit.Assert.assertThat; @@ -49,6 +53,8 @@ import static org.mockito.Mockito.when;  import static org.powermock.api.mockito.PowerMockito.mockStatic;  import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY;  import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.utils.FileHelper.createFile; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getFromPersistedProvider;  /**   * Created by cyberta on 29.01.18. @@ -343,22 +349,36 @@ public class MockHelper {          return resultReceiver;      } -    public static void mockConfigHelperForFileInputStream() throws FileNotFoundException { -        mockStatic(ConfigHelper.class); -        when(ConfigHelper.loadInputStreamAsString(any(InputStream.class))).thenCallRealMethod(); -        when(ConfigHelper.getInputStreamFrom(anyString())).thenAnswer(new Answer<InputStream>() { +    public static void mockInputStreamHelper() throws FileNotFoundException { +        mockStatic(InputStreamHelper.class); +        when(InputStreamHelper.loadInputStreamAsString(any(InputStream.class))).thenCallRealMethod(); +        when(InputStreamHelper.getInputStreamFrom(anyString())).thenAnswer(new Answer<InputStream>() {              @Override              public InputStream answer(InvocationOnMock invocation) throws Throwable {                  String filename = (String) invocation.getArguments()[0];                  return getClass().getClassLoader().getResourceAsStream(filename);              }          }); + +    } + +    public static void mockFileHelper(final File mockedFile) throws FileNotFoundException { +        mockStatic(FileHelper.class); +        when(createFile(any(File.class), anyString())).thenReturn(mockedFile);      } -    public static void mockConfigHelper(String mockedFingerprint, final Provider providerFromPrefs) throws CertificateEncodingException, NoSuchAlgorithmException { +    public static void mockConfigHelper(String mockedFingerprint) throws CertificateEncodingException, NoSuchAlgorithmException {          // FIXME use MockSharedPreferences instead of provider          mockStatic(ConfigHelper.class); -        when(ConfigHelper.getFromPersistedProvider(anyString(), anyString(), any(SharedPreferences.class))).thenAnswer(new Answer<String>() { +        when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint); +        when(ConfigHelper.checkErroneousDownload(anyString())).thenCallRealMethod(); +        when(ConfigHelper.parseX509CertificateFromString(anyString())).thenCallRealMethod(); +    } + +    public static void mockPreferenceHelper(final Provider providerFromPrefs) { +        // FIXME use MockSharedPreferences instead of provider +        mockStatic(PreferenceHelper.class); +        when(getFromPersistedProvider(anyString(), anyString(), any(SharedPreferences.class))).thenAnswer(new Answer<String>() {              @Override              public String answer(InvocationOnMock invocation) throws Throwable {                  String key = (String) invocation.getArguments()[0]; @@ -375,11 +395,8 @@ public class MockHelper {                  return null;              }          }); -        when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint); -        when(ConfigHelper.checkErroneousDownload(anyString())).thenCallRealMethod(); -        when(ConfigHelper.parseX509CertificateFromString(anyString())).thenCallRealMethod(); -        when(ConfigHelper.loadInputStreamAsString(any(InputStream.class))).thenCallRealMethod();      } +      public static void mockFingerprintForCertificate(String mockedFingerprint) throws CertificateEncodingException, NoSuchAlgorithmException {          mockStatic(ConfigHelper.class);          when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint); | 
