From 3c3421afd8f74a3aa8d1011de07a8c18f9549210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Tue, 8 Apr 2014 12:04:17 +0200 Subject: Rename app->bitmask_android This way, gradle commands generate apks correctly named. --- .../leap/bitmaskclient/test/ConnectionManager.java | 33 - .../test/testConfigurationWizard.java | 65 -- .../se/leap/bitmaskclient/test/testDashboard.java | 94 --- .../bitmaskclient/test/testLeapSRPSession.java | 636 --------------- app/src/main/AndroidManifest.xml | 89 --- app/src/main/assets/minivpn.armeabi | Bin 5216 -> 0 bytes app/src/main/assets/minivpn.armeabi-v7a | Bin 5228 -> 0 bytes app/src/main/assets/minivpn.mips | Bin 5164 -> 0 bytes app/src/main/assets/minivpn.x86 | Bin 5268 -> 0 bytes app/src/main/assets/urls/bitmask demo.url | 3 - .../java/org/jboss/security/srp/SRPParameters.java | 150 ---- .../org/spongycastle/util/encoders/Base64.java | 121 --- .../spongycastle/util/encoders/Base64Encoder.java | 298 ------- .../org/spongycastle/util/encoders/Encoder.java | 17 - .../util/io/pem/PemGenerationException.java | 26 - .../org/spongycastle/util/io/pem/PemHeader.java | 66 -- .../org/spongycastle/util/io/pem/PemObject.java | 62 -- .../util/io/pem/PemObjectGenerator.java | 7 - .../org/spongycastle/util/io/pem/PemWriter.java | 138 ---- .../java/se/leap/bitmaskclient/AboutActivity.java | 40 - .../java/se/leap/bitmaskclient/ConfigHelper.java | 194 ----- .../se/leap/bitmaskclient/ConfigurationWizard.java | 581 -------------- .../main/java/se/leap/bitmaskclient/Dashboard.java | 479 ----------- .../leap/bitmaskclient/DownloadFailedDialog.java | 94 --- app/src/main/java/se/leap/bitmaskclient/EIP.java | 623 --------------- .../se/leap/bitmaskclient/EipServiceFragment.java | 306 ------- .../java/se/leap/bitmaskclient/LeapHttpClient.java | 77 -- .../java/se/leap/bitmaskclient/LeapSRPSession.java | 341 -------- .../java/se/leap/bitmaskclient/LogInDialog.java | 151 ---- .../se/leap/bitmaskclient/NewProviderDialog.java | 123 --- .../main/java/se/leap/bitmaskclient/PRNGFixes.java | 338 -------- .../main/java/se/leap/bitmaskclient/Provider.java | 212 ----- .../java/se/leap/bitmaskclient/ProviderAPI.java | 875 --------------------- .../bitmaskclient/ProviderAPIResultReceiver.java | 56 -- .../leap/bitmaskclient/ProviderDetailFragment.java | 115 --- .../se/leap/bitmaskclient/ProviderListAdapter.java | 114 --- .../se/leap/bitmaskclient/ProviderListContent.java | 112 --- .../leap/bitmaskclient/ProviderListFragment.java | 234 ------ app/src/main/java/se/leap/openvpn/CIDRIP.java | 58 -- .../main/java/se/leap/openvpn/ConfigParser.java | 569 -------------- app/src/main/java/se/leap/openvpn/LICENSE.txt | 24 - app/src/main/java/se/leap/openvpn/LaunchVPN.java | 385 --------- app/src/main/java/se/leap/openvpn/LogWindow.java | 340 -------- .../java/se/leap/openvpn/NetworkSateReceiver.java | 86 -- app/src/main/java/se/leap/openvpn/OpenVPN.java | 250 ------ .../main/java/se/leap/openvpn/OpenVPNThread.java | 130 --- .../se/leap/openvpn/OpenVpnManagementThread.java | 592 -------------- .../main/java/se/leap/openvpn/OpenVpnService.java | 504 ------------ .../main/java/se/leap/openvpn/ProfileManager.java | 220 ------ .../main/java/se/leap/openvpn/ProxyDetection.java | 54 -- .../main/java/se/leap/openvpn/VPNLaunchHelper.java | 76 -- app/src/main/java/se/leap/openvpn/VpnProfile.java | 758 ------------------ app/src/main/res/drawable-hdpi/ic_menu_add.png | Bin 2194 -> 0 bytes app/src/main/res/drawable-hdpi/ic_menu_login.png | Bin 1656 -> 0 bytes .../drawable-hdpi/ic_menu_settings_holo_light.png | Bin 1227 -> 0 bytes .../res/drawable-hdpi/ic_menu_trash_holo_light.png | Bin 1001 -> 0 bytes .../res/drawable-hdpi/ic_sysbar_quicksettings.png | Bin 773 -> 0 bytes .../main/res/drawable-hdpi/ic_vpn_disconnected.png | Bin 801 -> 0 bytes app/src/main/res/drawable-hdpi/icon.png | Bin 7965 -> 0 bytes app/src/main/res/drawable-ldpi/ic_menu_add.png | Bin 1580 -> 0 bytes app/src/main/res/drawable-ldpi/ic_menu_login.png | Bin 1512 -> 0 bytes app/src/main/res/drawable-ldpi/ic_stat_vpn.png | Bin 461 -> 0 bytes .../main/res/drawable-ldpi/ic_vpn_disconnected.png | Bin 455 -> 0 bytes app/src/main/res/drawable-ldpi/icon.png | Bin 3118 -> 0 bytes app/src/main/res/drawable-mdpi/ic_menu_add.png | Bin 1339 -> 0 bytes .../drawable-mdpi/ic_menu_settings_holo_light.png | Bin 866 -> 0 bytes .../res/drawable-mdpi/ic_menu_trash_holo_light.png | Bin 746 -> 0 bytes .../res/drawable-mdpi/ic_sysbar_quicksettings.png | Bin 653 -> 0 bytes .../main/res/drawable-mdpi/ic_vpn_disconnected.png | Bin 586 -> 0 bytes app/src/main/res/drawable-mdpi/icon.png | Bin 4518 -> 0 bytes app/src/main/res/drawable-xhdpi/ic_menu_login.png | Bin 2178 -> 0 bytes .../drawable-xhdpi/ic_menu_settings_holo_light.png | Bin 1622 -> 0 bytes .../drawable-xhdpi/ic_menu_trash_holo_light.png | Bin 1279 -> 0 bytes .../res/drawable-xhdpi/ic_sysbar_quicksettings.png | Bin 956 -> 0 bytes .../res/drawable-xhdpi/ic_vpn_disconnected.png | Bin 1091 -> 0 bytes app/src/main/res/drawable-xhdpi/icon.png | Bin 11135 -> 0 bytes app/src/main/res/layout-xlarge/about.xml | 134 ---- .../main/res/layout-xlarge/client_dashboard.xml | 69 -- .../configuration_wizard_activity.xml | 27 - .../res/layout-xlarge/eip_service_fragment.xml | 75 -- app/src/main/res/layout-xlarge/log_in_dialog.xml | 39 - app/src/main/res/layout-xlarge/logwindow.xml | 17 - .../main/res/layout-xlarge/new_provider_dialog.xml | 26 - .../res/layout-xlarge/provider_detail_fragment.xml | 43 - .../res/layout-xlarge/provider_list_fragment.xml | 16 - .../main/res/layout-xlarge/provider_list_item.xml | 44 -- app/src/main/res/layout/about.xml | 122 --- app/src/main/res/layout/client_dashboard.xml | 67 -- .../res/layout/configuration_wizard_activity.xml | 26 - app/src/main/res/layout/eip_service_fragment.xml | 71 -- app/src/main/res/layout/log_in_dialog.xml | 41 - app/src/main/res/layout/logwindow.xml | 17 - app/src/main/res/layout/new_provider_dialog.xml | 24 - .../main/res/layout/provider_detail_fragment.xml | 40 - app/src/main/res/layout/provider_list_fragment.xml | 15 - app/src/main/res/layout/provider_list_item.xml | 43 - app/src/main/res/menu/client_dashboard.xml | 23 - .../res/menu/configuration_wizard_activity.xml | 15 - app/src/main/res/menu/logmenu.xml | 34 - app/src/main/res/values-ca/arrays.xml | 3 - app/src/main/res/values-ca/strings.xml | 41 - app/src/main/res/values-cs/arrays.xml | 3 - app/src/main/res/values-cs/strings.xml | 79 -- app/src/main/res/values-de/arrays.xml | 3 - app/src/main/res/values-de/strings.xml | 80 -- app/src/main/res/values-es/arrays.xml | 3 - app/src/main/res/values-es/strings.xml | 60 -- app/src/main/res/values-et/arrays.xml | 3 - app/src/main/res/values-et/strings.xml | 66 -- app/src/main/res/values-fr/arrays.xml | 3 - app/src/main/res/values-fr/strings.xml | 65 -- app/src/main/res/values-id/arrays.xml | 3 - app/src/main/res/values-id/strings.xml | 80 -- app/src/main/res/values-it/arrays.xml | 3 - app/src/main/res/values-it/strings.xml | 66 -- app/src/main/res/values-ja/arrays.xml | 3 - app/src/main/res/values-ja/strings.xml | 82 -- app/src/main/res/values-ko/arrays.xml | 3 - app/src/main/res/values-ko/strings.xml | 65 -- app/src/main/res/values-nl/arrays.xml | 3 - app/src/main/res/values-nl/strings.xml | 33 - app/src/main/res/values-no/arrays.xml | 3 - app/src/main/res/values-no/strings.xml | 38 - app/src/main/res/values-ro/arrays.xml | 3 - app/src/main/res/values-ro/strings.xml | 51 -- app/src/main/res/values-ru/arrays.xml | 3 - app/src/main/res/values-ru/strings.xml | 80 -- app/src/main/res/values-uk/arrays.xml | 3 - app/src/main/res/values-uk/strings.xml | 81 -- app/src/main/res/values-zh-rCN/arrays.xml | 3 - app/src/main/res/values-zh-rCN/strings.xml | 52 -- app/src/main/res/values-zh-rTW/arrays.xml | 3 - app/src/main/res/values-zh-rTW/strings.xml | 52 -- app/src/main/res/values/arrays.xml | 4 - app/src/main/res/values/attrs.xml | 8 - app/src/main/res/values/strings.xml | 143 ---- app/src/main/res/values/styles.xml | 37 - app/src/main/res/values/untranslatable.xml | 19 - 138 files changed, 13277 deletions(-) delete mode 100644 app/src/androidTest/java/se/leap/bitmaskclient/test/ConnectionManager.java delete mode 100644 app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java delete mode 100644 app/src/androidTest/java/se/leap/bitmaskclient/test/testDashboard.java delete mode 100644 app/src/androidTest/java/se/leap/bitmaskclient/test/testLeapSRPSession.java delete mode 100644 app/src/main/AndroidManifest.xml delete mode 100644 app/src/main/assets/minivpn.armeabi delete mode 100644 app/src/main/assets/minivpn.armeabi-v7a delete mode 100644 app/src/main/assets/minivpn.mips delete mode 100644 app/src/main/assets/minivpn.x86 delete mode 100644 app/src/main/assets/urls/bitmask demo.url delete mode 100644 app/src/main/java/org/jboss/security/srp/SRPParameters.java delete mode 100644 app/src/main/java/org/spongycastle/util/encoders/Base64.java delete mode 100644 app/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java delete mode 100644 app/src/main/java/org/spongycastle/util/encoders/Encoder.java delete mode 100644 app/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java delete mode 100644 app/src/main/java/org/spongycastle/util/io/pem/PemHeader.java delete mode 100644 app/src/main/java/org/spongycastle/util/io/pem/PemObject.java delete mode 100644 app/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java delete mode 100644 app/src/main/java/org/spongycastle/util/io/pem/PemWriter.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/AboutActivity.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/ConfigurationWizard.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/Dashboard.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/EIP.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/LeapHttpClient.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/LogInDialog.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/NewProviderDialog.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/PRNGFixes.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/Provider.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/ProviderAPIResultReceiver.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/ProviderDetailFragment.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/ProviderListContent.java delete mode 100644 app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java delete mode 100644 app/src/main/java/se/leap/openvpn/CIDRIP.java delete mode 100644 app/src/main/java/se/leap/openvpn/ConfigParser.java delete mode 100644 app/src/main/java/se/leap/openvpn/LICENSE.txt delete mode 100644 app/src/main/java/se/leap/openvpn/LaunchVPN.java delete mode 100644 app/src/main/java/se/leap/openvpn/LogWindow.java delete mode 100644 app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java delete mode 100644 app/src/main/java/se/leap/openvpn/OpenVPN.java delete mode 100644 app/src/main/java/se/leap/openvpn/OpenVPNThread.java delete mode 100644 app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java delete mode 100644 app/src/main/java/se/leap/openvpn/OpenVpnService.java delete mode 100644 app/src/main/java/se/leap/openvpn/ProfileManager.java delete mode 100644 app/src/main/java/se/leap/openvpn/ProxyDetection.java delete mode 100644 app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java delete mode 100644 app/src/main/java/se/leap/openvpn/VpnProfile.java delete mode 100644 app/src/main/res/drawable-hdpi/ic_menu_add.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_menu_login.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_menu_trash_holo_light.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_sysbar_quicksettings.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png delete mode 100644 app/src/main/res/drawable-hdpi/icon.png delete mode 100644 app/src/main/res/drawable-ldpi/ic_menu_add.png delete mode 100644 app/src/main/res/drawable-ldpi/ic_menu_login.png delete mode 100644 app/src/main/res/drawable-ldpi/ic_stat_vpn.png delete mode 100644 app/src/main/res/drawable-ldpi/ic_vpn_disconnected.png delete mode 100644 app/src/main/res/drawable-ldpi/icon.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_menu_add.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_menu_settings_holo_light.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_menu_trash_holo_light.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_sysbar_quicksettings.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_vpn_disconnected.png delete mode 100644 app/src/main/res/drawable-mdpi/icon.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_menu_login.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_menu_settings_holo_light.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_menu_trash_holo_light.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_sysbar_quicksettings.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_vpn_disconnected.png delete mode 100644 app/src/main/res/drawable-xhdpi/icon.png delete mode 100644 app/src/main/res/layout-xlarge/about.xml delete mode 100644 app/src/main/res/layout-xlarge/client_dashboard.xml delete mode 100644 app/src/main/res/layout-xlarge/configuration_wizard_activity.xml delete mode 100644 app/src/main/res/layout-xlarge/eip_service_fragment.xml delete mode 100644 app/src/main/res/layout-xlarge/log_in_dialog.xml delete mode 100644 app/src/main/res/layout-xlarge/logwindow.xml delete mode 100644 app/src/main/res/layout-xlarge/new_provider_dialog.xml delete mode 100644 app/src/main/res/layout-xlarge/provider_detail_fragment.xml delete mode 100644 app/src/main/res/layout-xlarge/provider_list_fragment.xml delete mode 100644 app/src/main/res/layout-xlarge/provider_list_item.xml delete mode 100644 app/src/main/res/layout/about.xml delete mode 100644 app/src/main/res/layout/client_dashboard.xml delete mode 100644 app/src/main/res/layout/configuration_wizard_activity.xml delete mode 100644 app/src/main/res/layout/eip_service_fragment.xml delete mode 100644 app/src/main/res/layout/log_in_dialog.xml delete mode 100644 app/src/main/res/layout/logwindow.xml delete mode 100644 app/src/main/res/layout/new_provider_dialog.xml delete mode 100644 app/src/main/res/layout/provider_detail_fragment.xml delete mode 100644 app/src/main/res/layout/provider_list_fragment.xml delete mode 100644 app/src/main/res/layout/provider_list_item.xml delete mode 100644 app/src/main/res/menu/client_dashboard.xml delete mode 100644 app/src/main/res/menu/configuration_wizard_activity.xml delete mode 100644 app/src/main/res/menu/logmenu.xml delete mode 100644 app/src/main/res/values-ca/arrays.xml delete mode 100644 app/src/main/res/values-ca/strings.xml delete mode 100644 app/src/main/res/values-cs/arrays.xml delete mode 100644 app/src/main/res/values-cs/strings.xml delete mode 100644 app/src/main/res/values-de/arrays.xml delete mode 100644 app/src/main/res/values-de/strings.xml delete mode 100644 app/src/main/res/values-es/arrays.xml delete mode 100644 app/src/main/res/values-es/strings.xml delete mode 100644 app/src/main/res/values-et/arrays.xml delete mode 100644 app/src/main/res/values-et/strings.xml delete mode 100644 app/src/main/res/values-fr/arrays.xml delete mode 100644 app/src/main/res/values-fr/strings.xml delete mode 100644 app/src/main/res/values-id/arrays.xml delete mode 100644 app/src/main/res/values-id/strings.xml delete mode 100644 app/src/main/res/values-it/arrays.xml delete mode 100644 app/src/main/res/values-it/strings.xml delete mode 100644 app/src/main/res/values-ja/arrays.xml delete mode 100644 app/src/main/res/values-ja/strings.xml delete mode 100644 app/src/main/res/values-ko/arrays.xml delete mode 100644 app/src/main/res/values-ko/strings.xml delete mode 100644 app/src/main/res/values-nl/arrays.xml delete mode 100644 app/src/main/res/values-nl/strings.xml delete mode 100644 app/src/main/res/values-no/arrays.xml delete mode 100644 app/src/main/res/values-no/strings.xml delete mode 100644 app/src/main/res/values-ro/arrays.xml delete mode 100644 app/src/main/res/values-ro/strings.xml delete mode 100644 app/src/main/res/values-ru/arrays.xml delete mode 100644 app/src/main/res/values-ru/strings.xml delete mode 100644 app/src/main/res/values-uk/arrays.xml delete mode 100644 app/src/main/res/values-uk/strings.xml delete mode 100644 app/src/main/res/values-zh-rCN/arrays.xml delete mode 100644 app/src/main/res/values-zh-rCN/strings.xml delete mode 100644 app/src/main/res/values-zh-rTW/arrays.xml delete mode 100644 app/src/main/res/values-zh-rTW/strings.xml delete mode 100644 app/src/main/res/values/arrays.xml delete mode 100644 app/src/main/res/values/attrs.xml delete mode 100644 app/src/main/res/values/strings.xml delete mode 100644 app/src/main/res/values/styles.xml delete mode 100644 app/src/main/res/values/untranslatable.xml (limited to 'app/src') diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/ConnectionManager.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/ConnectionManager.java deleted file mode 100644 index f1cbff19..00000000 --- a/app/src/androidTest/java/se/leap/bitmaskclient/test/ConnectionManager.java +++ /dev/null @@ -1,33 +0,0 @@ -package se.leap.bitmaskclient.test; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.wifi.WifiManager; -import android.util.Log; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public class ConnectionManager { - static void setMobileDataEnabled(boolean enabled, Context context) { - final ConnectivityManager conman = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - Method[] methods = conman.getClass().getMethods(); - for (Method method : methods) { - if (method.getName().equals("setMobileDataEnabled")) { - method.setAccessible(true); - try { - method.invoke(conman, enabled); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - } - } - - static void setWifiEnabled(boolean enabled, Context context) { - WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - wifiManager.setWifiEnabled(enabled); - } -} diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java deleted file mode 100644 index 847d8cdd..00000000 --- a/app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java +++ /dev/null @@ -1,65 +0,0 @@ -package se.leap.bitmaskclient.test; - -import android.test.ActivityInstrumentationTestCase2; -import android.widget.ListView; -import com.jayway.android.robotium.solo.Solo; -import java.io.IOException; -import se.leap.bitmaskclient.AboutActivity; -import se.leap.bitmaskclient.ConfigurationWizard; -import se.leap.bitmaskclient.ProviderDetailFragment; -import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.test.ConnectionManager; - -public class testConfigurationWizard extends ActivityInstrumentationTestCase2 { - - private Solo solo; - private static int added_providers; - - public testConfigurationWizard() { - super(ConfigurationWizard.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - solo = new Solo(getInstrumentation(), getActivity()); - ConnectionManager.setMobileDataEnabled(true, solo.getCurrentActivity().getApplicationContext()); - } - - @Override - protected void tearDown() throws Exception { - solo.finishOpenedActivities(); - } - - public void testListProviders() throws IOException { - assertEquals(solo.getCurrentViews(ListView.class).size(), 1); - - int number_of_available_providers = solo.getCurrentViews(ListView.class).get(0).getCount(); - - assertEquals("Number of available providers differ", solo.getCurrentActivity().getAssets().list("urls").length + added_providers, number_of_available_providers); - } - - public void testSelectProvider() { - solo.clickOnText("bitmask"); - assertTrue("Provider details dialog did not appear", solo.waitForFragmentByTag(ProviderDetailFragment.TAG, 60*1000)); - } - - public void testAddNewProvider() { - solo.clickOnActionBarItem(R.id.new_provider); - solo.enterText(0, "dev.bitmask.net"); - solo.clickOnCheckBox(0); - solo.clickOnText(solo.getString(R.string.save)); - added_providers = added_providers+1; - assertTrue("Provider details dialog did not appear", solo.waitForFragmentByTag(ProviderDetailFragment.TAG, 60*1000)); - solo.goBack(); - } - - public void testShowAbout() { - solo.clickOnMenuItem(solo.getString(R.string.about)); - assertTrue("Provider details dialog did not appear", solo.waitForActivity(AboutActivity.class)); - } - - public void testShowSettings() { - //TODO We still don't have the settings button - } -} diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/testDashboard.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/testDashboard.java deleted file mode 100644 index 269f1d18..00000000 --- a/app/src/androidTest/java/se/leap/bitmaskclient/test/testDashboard.java +++ /dev/null @@ -1,94 +0,0 @@ -package se.leap.bitmaskclient.test; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.provider.Settings; -import android.test.ActivityInstrumentationTestCase2; -import android.util.Log; -import com.jayway.android.robotium.solo.Solo; -import se.leap.bitmaskclient.ConfigurationWizard; -import se.leap.bitmaskclient.Dashboard; -import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.test.ConnectionManager; - -public class testDashboard extends ActivityInstrumentationTestCase2 { - - private Solo solo; - - public testDashboard() { - super(Dashboard.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - solo = new Solo(getInstrumentation(), getActivity()); - ConnectionManager.setMobileDataEnabled(true, solo.getCurrentActivity().getApplicationContext()); - } - - @Override - protected void tearDown() throws Exception { - solo.finishOpenedActivities(); - } - - /** - * This test will fail if Android does not trust VPN connection. - * I cannot automate that dialog. - */ - public void testOnOffOpenVpn() { - solo.clickOnView(solo.getView(R.id.eipSwitch)); - if(!solo.waitForText(getActivity().getString(R.string.eip_status_start_pending))) - fail(); - if(!solo.waitForText(getActivity().getString(R.string.state_auth))) - fail(); - if(!solo.waitForText(getActivity().getString(R.string.eip_state_connected), 1, 30*1000)) - fail(); - - solo.clickOnView(solo.getView(R.id.eipSwitch)); - if(!solo.waitForText(getActivity().getString(R.string.eip_state_not_connected))) - fail(); - - ConnectionManager.setMobileDataEnabled(false, solo.getCurrentActivity().getApplicationContext()); - - solo.clickOnView(solo.getView(R.id.eipSwitch)); - if(!solo.waitForText(getActivity().getString(R.string.eip_status_start_pending))) - fail(); - if(!solo.waitForText(getActivity().getString(R.string.state_nonetwork))) - fail(); - - } - - public void testLogInAndOut() { - long miliseconds_to_log_in = 40 * 1000; - solo.clickOnActionBarItem(R.id.login_button); - solo.enterText(0, "parmegvtest1"); - solo.enterText(1, " S_Zw3'-"); - solo.clickOnText("Log In"); - solo.waitForDialogToClose(); - solo.waitForDialogToClose(miliseconds_to_log_in); - if(!solo.waitForText(getActivity().getString(R.string.succesful_authentication_message))) - fail(); - - solo.clickOnActionBarItem(R.string.logout_button); - if(!solo.waitForDialogToClose()) - fail(); - } - - public void testShowAbout() { - solo.clickOnMenuItem(getActivity().getString(R.string.about)); - solo.waitForText(getActivity().getString(R.string.repository_url_text)); - solo.goBack(); - - solo.clickOnMenuItem(getActivity().getString(R.string.about)); - solo.waitForText(getActivity().getString(R.string.repository_url_text)); - solo.goBack(); - } - - public void testSwitchProvider() { - solo.clickOnMenuItem(getActivity().getString(R.string.switch_provider_menu_option)); - solo.waitForActivity(ConfigurationWizard.class); - solo.goBack(); - } -} diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/testLeapSRPSession.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/testLeapSRPSession.java deleted file mode 100644 index 88d70da4..00000000 --- a/app/src/androidTest/java/se/leap/bitmaskclient/test/testLeapSRPSession.java +++ /dev/null @@ -1,636 +0,0 @@ -package se.leap.bitmaskclient.test; - -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import org.jboss.security.srp.SRPParameters; - -import android.test.suitebuilder.annotation.SmallTest; - -import se.leap.bitmaskclient.ConfigHelper; -import se.leap.bitmaskclient.LeapSRPSession; - -import junit.framework.TestCase; - -public class testLeapSRPSession extends TestCase { - - public testLeapSRPSession(String name) { - super(name); - } - - protected void setUp() throws Exception { - super.setUp(); - } - - protected void tearDown() throws Exception { - super.tearDown(); - } - - public void testExponential() { - byte[] expected_A; - byte[] a_byte; - SRPParameters params; - LeapSRPSession client; - - /* Test 1: abytes = 4 */ - expected_A = new BigInteger("44eba0239ddfcc5a488d208df32a89eb00e93e6576b22ba2e4410085a413cf64e9c2f08ebc36a788a0761391150ad4a0507ca43f9ca659e2734f0457a85358c0bb39fa87183c9d3f9f8a3b148dab6303a4e796294f3e956472ba0e2ea5697382acd93c8b8f1b3a7a9d8517eebffd6301bfc8de7f7b701f0878a71faae1e25ad4", 16).toByteArray(); - String username = "username", - password = "password", - salt = "64c3289d04a6ecad", - a = "3565fdc2"; - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - - byte[] A = client.exponential(); - - assertTrue(Arrays.equals(A, expected_A)); - - /* Test 1: abytes = 5 */ - a = "67c152a3"; - expected_A = new BigInteger("11acfacc08178d48f95c0e69adb11f6d144dd0980ee6e44b391347592e3bd5e9cb841d243b3d9ac2adb25b367a2558e8829b22dcef96c0934378412383ccf95141c3cb5f17ada20f53a0225f56a07f2b0c0469ed6bbad3646f7b71bdd4bedf5cc6fac244b26d3195d8f55877ff94a925b0c0c8f7273eca733c0355b38360442e", 16).toByteArray(); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - - A = client.exponential(); - - assertTrue(Arrays.equals(A, expected_A)); - } - - public void testResponse() throws NoSuchAlgorithmException { - /* Test 1: with intermediate checks */ - byte[] expected_A = trim(new BigInteger("884380f70a62193bbe3589c4e1dbdc4467b6b5a1b4486e4b779023506fc1f885ae26fa4a5d817b3f38a35f3487b147b82d4bd0069faa64fdc845f7494a78251709e212698e42ced44b0f3849adc73f467afcb26983bd13bdc38906b178003373ddd0ac1d38ce8a39ffa3a7795787207a129a784f4b65ce0b302eb1bcf4045883", 16).toByteArray()); - byte[] expected_x = new BigInteger("4cb937fd74ee3bb53b79a3174d0c07c14131de9c825897cbca52154e74200602", 16).toByteArray(); - byte[] expected_M1 = trim(new BigInteger("e6a8efca2c07ef24e0b69be2d4d4a7e74742a4db7a92228218fec0008f7cc94b", 16).toByteArray()); - String username = "username", - password = "password", - salt = "64c3289d04a6ecad", - a = "8c911355"; - byte[] a_byte = new BigInteger(a, 16).toByteArray(); - SRPParameters params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - LeapSRPSession client = new LeapSRPSession(username, password, params, a_byte); - - byte[] x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); - assertTrue(Arrays.equals(x, expected_x)); - - byte[] A = client.exponential(); - assertTrue(Arrays.equals(A, expected_A)); - - String B = "bc745ba25564fc312f44ea09fb663aa6d95867772e412a6a23f1bc24183e54b32f134372c560f4b3fda19ba7a56b0f84fdcdecc22be6fd256639e918e019691c40a39aa5c9631820e42b28da61b8c75b45afae9d77d63ac8f4dda093762be4a890fbd86061dbd7e5e7c03c4dacde769e0f564df00403e449c0535537f1ba7263"; - - byte[] M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - assertTrue(Arrays.equals(M1, expected_M1)); - - /* Test 2: no intermediate checks */ - expected_A = trim(new BigInteger("9ffc407afd7e7ecd32a8ea68aa782b0254a7e2197a955b5aa646fc1fc43ff6ef2239f01b7d5b82f152c870d3e69f3321878ca2acda06dd8fb6ce02f41c7ed48061c78697b01cf353f4222311334c707358b6ec067e317527316bfa85b5ec74537e38b5b14c1100d14c96320f385e5b1dcccde07e728c7ef624353167a29ae461", 16).toByteArray()); - expected_M1 = trim(new BigInteger("c3203ec1dd55c96038276456c18c447fb4d2a2f896c73c31d56da1781cae79a8", 16).toByteArray()); - a = "38d5b211"; - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); - A = client.exponential(); - - B = "b8ca7d93dbe2478966ffe025a9f2fb43b9995ce04af9523ea9a3fa4b132136076aa66ead1597c3da23f477ce1cfaf68b5dcc94e146db06cf8391d14a76ce53aab86067b13c93b135d7be6275669b3f51afec6cc41f19e0afca7c3ad5c4d6ee4c09d4b11bcd12e26c727ee56d173b92eea6926e72cc73deebe12dd6f30f44db8a"; - M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - /* Test 3: With intermediate values */ - expected_M1 = new BigInteger("4c01f65a9bb00f95e435593083040ae1e59e59800c598b42de821c21f3a35223", 16).toByteArray(); - expected_A = new BigInteger("1bceab3047a6f84495fdd5b4dbe891f0b30f870d7d4e38eaef728f6a7d4e9342d8dae8502fdae4f16b718d2e916a38b16b6def45559a5ebae417a1b115ba6b6a0451c7ff174c3e2507d7d1f18ef646fd065bc9ba165a2a0ae4d6da54f060427178b95b0eff745f5c3f8c4f19ea35addc3ce0daf2aca3328c98bafcf98286d115", 16).toByteArray(); - B = "41a7b384f2f52312fa79b9dc650ae894f543aea49800cf9477fbcf63e39cbfe6d422f7126777d645cdf749276a3ae9eb6dfcfdb887f8f60ac4094a343013fcebbd40e95b3153f403ab7bb21ea1315aa018bab6ab84017fcb084b3870a8bf1cb717b39c9a28177c61ce7d1738379be9d192dd9793b50ebc3afabe5e78b0a4b017"; - a = "36ee80ec"; - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); - A = client.exponential(); - - M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - } - - public void testCalculateV() throws NoSuchAlgorithmException { - String expected_v = "502f3ffddc78b866330550c2c60ebd68427c1793237d770e6390d1f794abd47b6786fa5025728d1ca4ec24cfc7a89808278330099ad66456a7c9c88be570b928f9825ac2ecdee31792335f7fa5fc9a78b692c487aa400c7d5cc5c1f2a3a74634c4afa0159600bbf22bf6dfb1e0d85061e55ce8df6243758066503bcf51c83848cf7184209731f89a90d888934c75798828859babe73c17009bf827723fc1bcd0"; - - BigInteger k = new BigInteger("bf66c44a428916cad64aa7c679f3fd897ad4c375e9bbb4cbf2f5de241d618ef0", 16); - BigInteger g = new BigInteger("2", 16); - BigInteger N = new BigInteger(ConfigHelper.NG_1024, 16); - BigInteger x = new BigInteger("4cb937fd74ee3bb53b79a3174d0c07c14131de9c825897cbca52154e74200602", 16); - - BigInteger v = k.multiply(g.modPow(x, N)); // g^x % N - - assertEquals(v.toString(16), expected_v); - assertTrue(Arrays.equals(v.toByteArray(), new BigInteger(expected_v, 16).toByteArray())); - } - - public void testGetU() throws NoSuchAlgorithmException { - /* Test 1 */ - String Astr = "46b1d1fe038a617966821bd5bb6af967be1bcd6f54c2db5a474cb80b625870e4616953271501a82198d0c14e72b95cdcfc9ec867027b0389aacb313319d4e81604ccf09ce7841dc333be2e03610ae46ec0c8e06b8e86975e0984cae4d0b61c51f1fe5499a4d4d42460261a3e134f841f2cef4d68a583130ee8d730e0b51a858f"; - String Bstr = "5e1a9ac84b1d9212a0d8f8fe444a34e7da4556a1ef5aebc043ae7276099ccdb305fd7e1c179729e24b484a35c0e33b6a898477590b93e9a4044fc1b8d6bc73db8ac7778f546b25ec3f22e92ab7144e5b974dc58e82a333262063062b6944a2e4393d2a087e4906e6a8cfa0fdfd8a5e5930b7cdb45435cbee7c49dfa1d1216881"; - String ustr = "759c3cfb6bfaccf07eeb8e46fe6ea290291d4d32faca0681830a372983ab0a61"; - - byte[] Abytes = new BigInteger(Astr, 16).toByteArray(); - byte[] Bbytes = new BigInteger(Bstr, 16).toByteArray(); - byte[] expected_u = new BigInteger(ustr, 16).toByteArray(); - - MessageDigest u_digest = MessageDigest.getInstance("SHA256"); - u_digest.update(trim(Abytes)); - u_digest.update(trim(Bbytes)); - byte[] u = new BigInteger(1, u_digest.digest()).toByteArray(); - - assertTrue(Arrays.equals(expected_u, u)); - - /* Test 2 */ - Astr = "c4013381bdb2fdd901944b9d823360f367c52635b576b9a50d2db77141d357ed391c3ac5fa452c2bbdc35f96bfed21df61627b40aed8f67f21ebf81e5621333f44049d6c9f6ad36464041438350e1f86000a8e3bfb63d4128c18322d2517b0d3ead63fd504a9c8f2156d46e64268110cec5f3ccab54a21559c7ab3ad67fedf90"; - Bstr = "e5d988752e8f265f01b98a1dcdecc4b685bd512e7cd9507f3c29f206c27dac91e027641eed1765c4603bbd7a9aa7fac300ef67dafe611ba2dbe29a32d83d486296f328d38b44c0c211d01d3fe422aac168b6850c87782338969c54594fc87804d4db34910ad4b5452a81027842ac8d8d8288fd44872e4c719ac8fb971d0a33e1"; - ustr = "6510328f913b81ba662e564ee5afb7c395ea27c3c0276fc5ca51f0edecd4baf1"; - - Abytes = new BigInteger(Astr, 16).toByteArray(); - Bbytes = new BigInteger(Bstr, 16).toByteArray(); - expected_u = new BigInteger(ustr, 16).toByteArray(); - expected_u = trim(expected_u); - - u_digest = MessageDigest.getInstance("SHA-256"); - u_digest.update(trim(Abytes)); - u_digest.update(trim(Bbytes)); - u = new BigInteger(1, u_digest.digest()).toByteArray(); - u = trim(u); - - assertTrue(Arrays.equals(expected_u, u)); - - /* Test 3 */ - Astr = "d13973fe4e0e13423cd036caf0912e23a1f9b0c23966f5a5897c8ff17c5cbac8bab7f07d9ac4ee47396a7c68e80ce854c84f243148521277900aaa132a7b93b61e54d742d7f36edb4cdef54bc78cca69ac72653a7ae0fc47ec1e9a84024ea9487a61357e28eddc185e4fe01388e64e6b8f688dd74471d56dd244204522e08483"; - Bstr = "a6701686d9d987a43f06e8497330c8add8febd191a7a975bced0d058eb03ccc6805263349363b2d54ac435b01155dc41c6067287d9b93e3637ab3b7e8bc7d9cf38d9fdbb2ca9ee8ba1946a46cb555cb7dafcc177fcf7a4b0eb1e5db2249949c1fd15e0b7c1b3616f9e2649bdf074ed841efbdc9f29ee8c8bfcedeaed3dc49378"; - ustr = "78414ec80cf44225e7ed386dcf2ceb89837327ccae11b761fc77d48c0307977"; - - Abytes = new BigInteger(Astr, 16).toByteArray(); - Bbytes = new BigInteger(Bstr, 16).toByteArray(); - expected_u = new BigInteger(ustr, 16).toByteArray(); - expected_u = trim(expected_u); - - u_digest = MessageDigest.getInstance("SHA-256"); - u_digest.update(trim(Abytes)); - u_digest.update(trim(Bbytes)); - u = new BigInteger(1, u_digest.digest()).toByteArray(); - u = trim(u); - - assertTrue(Arrays.equals(expected_u, u)); - - /* Test 4 */ - Astr = "ee8bc0cb97dd9c9937759658ff9d791df1dd57b48c5febc2e98af028d0e36eaddf1a3fc555f2bcd6456827e8c7b07ec02a1f365457843bda226bfc1a55c4776879f9df6c916810131ec65a3a4cf473c6a34299d64c91cf26542ea0fc059d24422fc783460c3fafe26bf6f7c24904ae1c5a6421e2f5315030ab007ce8f2c2fd97"; - Bstr = "95ecbd13b28c7f38318fd664ee97d9e853b0d6e9cbff9a3775a3cc5d5077ffc146aec70d9439e75c19a34b67368b8bd7035ba6254e0a260d99b1e253aae2e0d8f4a13e1ed472f3ef0e3086300cd15d059f6be7d7141ee09071b1c5e5d1c83b250a3c8f1a587f8fe59d49aaeb2cfc7e13a5a58bc76cc8baf7f6a647982c67ee49"; - ustr = "e28737c7307c84e4d0866b7cf882f22852a764b109634f77a5eb986a96ffcf9a"; - - Abytes = new BigInteger(Astr, 16).toByteArray(); - Bbytes = new BigInteger(Bstr, 16).toByteArray(); - expected_u = new BigInteger(ustr, 16).toByteArray(); - expected_u = trim(expected_u); - assertEquals(new BigInteger(1, expected_u).toString(16), ustr); - - u_digest = MessageDigest.getInstance("SHA-256"); - u_digest.update(trim(Abytes)); - u_digest.update(trim(Bbytes)); - u = new BigInteger(1, u_digest.digest()).toByteArray(); - u = trim(u); - - assertTrue(Arrays.equals(expected_u, u)); - } - - @SmallTest - public void testCalculatePasswordHash() throws UnsupportedEncodingException, NoSuchAlgorithmException { - String salt_str = "", username_str = "", password_str = ""; - String expected_inner = "cfb9ae3ec5433076889c4fe5663926e20bf570cc7950a51c889a314fab2f5ed716bde9c1cc91be14", - expected_x = "9736a5e386a18f35bb08cac0f7c70bdbe120f2efe019874d0eb23b85b1955858"; - - /* Test 1 */ - salt_str = "cfb9ae3ec5433076"; username_str = "nostradamus"; password_str = "$[[//jjiilajfewahug43a89y¿"; - password_str = password_str.replaceAll("\\\\", "\\\\\\\\"); - // Calculate x = H(s | H(U | ':' | password)) - MessageDigest x_digest = MessageDigest.getInstance("SHA-256"); - - // Try to convert the username to a byte[] using UTF-8 - byte[] user = null; - byte[] password_bytes = null; - byte[] colon = {}; - - String encoding = "UTF-8"; - encoding = "ISO-8859-1"; - user = ConfigHelper.trim(username_str.getBytes(encoding)); - colon = ConfigHelper.trim(":".getBytes(encoding)); - password_bytes = ConfigHelper.trim(password_str.getBytes(encoding)); - - // Build the hash - x_digest.update(user); - x_digest.update(colon); - x_digest.update(password_bytes); - byte[] h = x_digest.digest(); - byte[] salt_bytes = ConfigHelper.trim(new BigInteger(salt_str, 16).toByteArray()); - - x_digest.reset(); - x_digest.update(salt_bytes); - x_digest.update(h); - byte[] x_digest_bytes = x_digest.digest(); - assertTrue(new BigInteger(1, x_digest_bytes).toString(16).equalsIgnoreCase(expected_x)); - } - - public void testCalculateS() throws NoSuchAlgorithmException { - String expected_S = "34d71467d0a30c5787e6b4384c7176a724df28f6c1d3b0b7738238621be19080942ca5d4454ab3e5664d42970ea2d42d1188ab246afb1475c481111f30886c79cc99ddd1d711034bc4ac57a0c134fef40470e1ac3386f39abcd1bf9d0cc758a1f38a87b2f7261018cdc6191cd941292c77c320b3c664a9bb1325dd7a2bf62db4"; - - String B_string = "d390da0991b84e20918764aed9bccf36fc70863f08a1d3fa0aecd9610d8c40d1895afaf3aa9b4582fb5bc673037dfc1bf0ca7d3d190a807dec563fe57d596b3551ec8a6461e0621b77fabfaa29234187063e715e2706522c65f35d89f3914a7cdf40ddd240ef3d22b5534469cf6b3b31e158f817dcf5b6b44b0914d4787d027e"; - String A_string = "c6385e56b9cb8987df3c58ffe6c47bfcd9f9d52f02b68048d72135a8522a0919b41683e3c732a0874e0555b8f288fa067842b1c40a56f1a7d910ad125e71238cb14ba47838f881cabf735d5414f4fcdd6855943621c271c5f013753d439fb124674c79e30d849e9178c780f7c25aded12f053ae41be68289787caf7c3cbb07a4"; - String a_string = "fc1c2d8d"; - - BigInteger A = new BigInteger(A_string, 16); - byte[] Bbytes = new BigInteger(B_string, 16).toByteArray(); - BigInteger a = new BigInteger(a_string, 16); - BigInteger g = ConfigHelper.G; - BigInteger N = new BigInteger(ConfigHelper.NG_1024, 16); - BigInteger k = new BigInteger("bf66c44a428916cad64aa7c679f3fd897ad4c375e9bbb4cbf2f5de241d618ef0", 16); - BigInteger x = new BigInteger("4cb937fd74ee3bb53b79a3174d0c07c14131de9c825897cbca52154e74200602", 16); - - /* GetU */ - MessageDigest u_digest = MessageDigest.getInstance("SHA256"); - u_digest.update(trim(A.toByteArray())); - u_digest.update(trim(Bbytes)); - byte[] ub = new BigInteger(1, u_digest.digest()).toByteArray(); - - BigInteger v = k.multiply(g.modPow(x, N)); // g^x % N - String expected_v = "502f3ffddc78b866330550c2c60ebd68427c1793237d770e6390d1f794abd47b6786fa5025728d1ca4ec24cfc7a89808278330099ad66456a7c9c88be570b928f9825ac2ecdee31792335f7fa5fc9a78b692c487aa400c7d5cc5c1f2a3a74634c4afa0159600bbf22bf6dfb1e0d85061e55ce8df6243758066503bcf51c83848cf7184209731f89a90d888934c75798828859babe73c17009bf827723fc1bcd0"; - String v_string = v.toString(16); - - BigInteger B = new BigInteger(1, Bbytes); - BigInteger u = new BigInteger(1, ub); - String expected_u_string = "2d36f816df24da7b904c904a7e2a2500511df118ced26bda92a63aca792c93b"; - String u_string = u.toString(16); - - BigInteger B_v = B.subtract(v); - BigInteger a_ux = a.add(u.multiply(x)); - byte[] a_ux_byte= a_ux.toByteArray(); - String expected_a_aux_string = "d8d0843a60d471ab46a761b775f8fd28bbbd8affbc704424c79b822ac36b1177925404db910072737df3c5083cf20ebd2f08e1381d80d91e4f6fc99f592203"; - byte[] expected_a_aux_byte = new BigInteger(expected_a_aux_string, 16).toByteArray(); - - BigInteger S = B_v.modPow(a_ux, N); - - byte[] expected_S_bytes = new BigInteger(expected_S, 16).toByteArray(); - byte[] S_bytes = S.toByteArray(); - assertTrue(Arrays.equals(S_bytes, expected_S_bytes)); - assertEquals(S.toString(16), expected_S); - } - - public void testXor() throws Exception { - String expected_xor_string = "928ade491bc87bba9eb578701d44d30ed9080e60e542ba0d3b9c20ded9f592bf"; - byte[] expected_xor = new BigInteger(expected_xor_string, 16).toByteArray(); - - byte[] nbytes = trim(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray()); - byte[] gbytes = trim(ConfigHelper.G.toByteArray()); - - byte[] ndigest = trim(MessageDigest.getInstance("SHA-256").digest(nbytes)); - byte[] gdigest = MessageDigest.getInstance("SHA-256").digest(gbytes); - - BigInteger ndigest_bigInteger = new BigInteger(1, ndigest); - String expected_ndigest_string = "494b6a801b379f37c9ee25d5db7cd70ffcfe53d01b7c9e4470eaca46bda24b39"; - String ndigest_string = ndigest_bigInteger.toString(16); - assertEquals(ndigest_string, expected_ndigest_string); - - BigInteger gdigest_bigInteger = new BigInteger(1, gdigest); - String xor_string = ndigest_bigInteger.xor(gdigest_bigInteger).toString(16); - - byte[] xor = new BigInteger(xor_string, 16).toByteArray(); - - assertTrue(Arrays.equals(expected_xor, xor)); - } - - public void testVerify() throws NoSuchAlgorithmException { - byte[] expected_A = trim(new BigInteger("884380f70a62193bbe3589c4e1dbdc4467b6b5a1b4486e4b779023506fc1f885ae26fa4a5d817b3f38a35f3487b147b82d4bd0069faa64fdc845f7494a78251709e212698e42ced44b0f3849adc73f467afcb26983bd13bdc38906b178003373ddd0ac1d38ce8a39ffa3a7795787207a129a784f4b65ce0b302eb1bcf4045883", 16).toByteArray()); - byte[] expected_x = new BigInteger("4cb937fd74ee3bb53b79a3174d0c07c14131de9c825897cbca52154e74200602", 16).toByteArray(); - byte[] expected_M1 = trim(new BigInteger("e6a8efca2c07ef24e0b69be2d4d4a7e74742a4db7a92228218fec0008f7cc94b", 16).toByteArray()); - byte[] expected_M2 = trim(new BigInteger("6402e108415ab4a7cd223ec435570614c8aacc09fcf081ade2dc00275e90ceee", 16).toByteArray()); - String username = "username", - password = "password", - salt = "64c3289d04a6ecad", - a = "8c911355"; - byte[] a_byte = new BigInteger(a, 16).toByteArray(); - SRPParameters params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - LeapSRPSession client = new LeapSRPSession(username, password, params, a_byte); - - byte[] x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); - assertTrue(Arrays.equals(x, expected_x)); - - byte[] A = client.exponential(); - assertTrue(Arrays.equals(A, expected_A)); - - String B = "bc745ba25564fc312f44ea09fb663aa6d95867772e412a6a23f1bc24183e54b32f134372c560f4b3fda19ba7a56b0f84fdcdecc22be6fd256639e918e019691c40a39aa5c9631820e42b28da61b8c75b45afae9d77d63ac8f4dda093762be4a890fbd86061dbd7e5e7c03c4dacde769e0f564df00403e449c0535537f1ba7263"; - - byte[] M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - assertTrue(Arrays.equals(M1, expected_M1)); - - boolean verified = client.verify(expected_M2); - assertTrue(verified); - - /* Test 2 */ - expected_A = trim(new BigInteger("180a1caf84efe93610a56772edea7b2d20ef3e9f34e578147b5402a898982f33131708233f9ddd2946246703c5db705f0859cca9cfc5b72ad5a05ec0c748545aa083d5b7b1bf06efe6737e9e0fd81b832b5cba983f1b9717041df8114385b93c8c669db06d62c5773b8e8a8f07e98a840a33d04d3448d4bcd2c042387c316750", 16).toByteArray()); - expected_M1 = trim(new BigInteger("a47782f23057a7e06704ea94389589b3c70971a63268acef2aefd74e234dd3c2", 16).toByteArray()); - a = "d89f0e33"; - expected_M2 = trim(new BigInteger("517278a03a0320a52dcb391caf5264d76149d7d9b71ed2b65536233344c550cf", 16).toByteArray()); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); - A = client.exponential(); - - B = "5f86fe2f7b7455e877e1760db8d3da1fcd4df0d10ec2a40298f87287bdb2f22c0ea54ff9b1f660cc1666459a7e2fd5501970b317490c3dfd3ba2e18f7be7526b72ea4d01e8f064754b935b107ced0892ce86112cbe32282f929907985fcb29f42c5d4dc32adeb29d12a611cac49cca3fefd2227efadc3989c2e72dd64a003141"; - M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - verified = client.verify(expected_M2); - assertTrue(verified); - - /* Test 3 */ - expected_A = trim(new BigInteger("a9c556c30bf4c1b1fdc1bc9e672ab4751806acc8581042b3779faaf25f85f47dfc58828742e2d2a06c51acbbb9f3fae0e01f64df0775a269f5ee4a6e71bc37b8a368e04b9053d399bc5b809ffd6ecab775a577804f2a5ed2e829f15e6af13bf0b78b6b108cf591bc9960992904fd1433698a51e0d05ee954cf98cbfe7995621e", 16).toByteArray()); - expected_M1 = trim(new BigInteger("0afca3583c4146990ec7312f9f4b4d9cceebc43a19f96709bf3d0a17b11dcc1e", 16).toByteArray()); - a = "50e662d6"; - expected_M2 = trim(new BigInteger("3bfb91c7d04b6da6381fe3d2648d992cdc6bc67b8ee16d1cfa733f786d492261", 16).toByteArray()); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); - A = client.exponential(); - - B = "6fe41e8262f4f8bc4ed9f4e1b4802ae3adac9c348e6efc07f16c6f5704b95a1f12325097489372c3936584a37301ebab400a32ac6699f4556da84f076489060527bd50578a317a3ec8b814bf2f4dd9c4adad368610eb638aa81663a205ba26d8f0b9654bf3940357b867cd42725e8532b97a2410a557d291aa55c0b44f249361"; - M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - verified = client.verify(expected_M2); - assertTrue(verified); - - /* Test 4: user abc, password abcdefghi */ - username = "abc"; - password = "abcdefghi"; - salt = "be26aac449a093e5"; - expected_A = trim(new BigInteger("c4013381bdb2fdd901944b9d823360f367c52635b576b9a50d2db77141d357ed391c3ac5fa452c2bbdc35f96bfed21df61627b40aed8f67f21ebf81e5621333f44049d6c9f6ad36464041438350e1f86000a8e3bfb63d4128c18322d2517b0d3ead63fd504a9c8f2156d46e64268110cec5f3ccab54a21559c7ab3ad67fedf90", 16).toByteArray()); - expected_x = trim(new BigInteger("6325967f1a161efd4e2d6e6fabbfccc32be05139cf82b08fb59c0a0db3f34bcf", 16).toByteArray()); - a = "5d4cde29"; - B = "e5d988752e8f265f01b98a1dcdecc4b685bd512e7cd9507f3c29f206c27dac91e027641eed1765c4603bbd7a9aa7fac300ef67dafe611ba2dbe29a32d83d486296f328d38b44c0c211d01d3fe422aac168b6850c87782338969c54594fc87804d4db34910ad4b5452a81027842ac8d8d8288fd44872e4c719ac8fb971d0a33e1"; - expected_M1 = trim(new BigInteger("e5972ddc53e6190735fc79cd823053a65ffb6041d69480adcba2f6a2dc2f2e86", 16).toByteArray()); - expected_M2 = trim(new BigInteger("8f4552b1021a4de621d8f50f0921c4d20651e702d9d71276f8f6c15b838de018", 16).toByteArray()); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - - x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); - assertTrue(Arrays.equals(x, expected_x)); - - A = client.exponential(); - assertTrue(Arrays.equals(A, expected_A)); - - M1 = client.response(trim(new BigInteger(salt, 16).toByteArray()), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - verified = client.verify(expected_M2); - assertTrue(verified); - - /* Test 5: user abc, password abcdefghi */ - username = "abc"; - password = "abcdefghi"; - salt = "be26aac449a093e5"; - expected_A = trim(new BigInteger("d13973fe4e0e13423cd036caf0912e23a1f9b0c23966f5a5897c8ff17c5cbac8bab7f07d9ac4ee47396a7c68e80ce854c84f243148521277900aaa132a7b93b61e54d742d7f36edb4cdef54bc78cca69ac72653a7ae0fc47ec1e9a84024ea9487a61357e28eddc185e4fe01388e64e6b8f688dd74471d56dd244204522e08483", 16).toByteArray()); - expected_x = trim(new BigInteger("6325967f1a161efd4e2d6e6fabbfccc32be05139cf82b08fb59c0a0db3f34bcf", 16).toByteArray()); - a = "fc57e4b1"; - B = "a6701686d9d987a43f06e8497330c8add8febd191a7a975bced0d058eb03ccc6805263349363b2d54ac435b01155dc41c6067287d9b93e3637ab3b7e8bc7d9cf38d9fdbb2ca9ee8ba1946a46cb555cb7dafcc177fcf7a4b0eb1e5db2249949c1fd15e0b7c1b3616f9e2649bdf074ed841efbdc9f29ee8c8bfcedeaed3dc49378"; - expected_M1 = trim(new BigInteger("0b590fde631566d0d3420a898a9b469656e64bfaff165c146b78964eee7920b8", 16).toByteArray()); - expected_M2 = trim(new BigInteger("04cf3ab3b75dbc4b116ca2fec949bf3deca1e360e016d7ab2b8a49904c534a27", 16).toByteArray()); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - - x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); - assertTrue(Arrays.equals(x, expected_x)); - - A = client.exponential(); - assertTrue(Arrays.equals(A, expected_A)); - - M1 = client.response(trim(new BigInteger(salt, 16).toByteArray()), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - verified = client.verify(expected_M2); - assertTrue(verified); - - /* Test 6: user and password of real use */ - username = "parmegv"; - password = "wR\"P}x@_,:k$`YYd*s T`-n."; - salt = "40c3f47b99ce8dc9"; - expected_A = trim(new BigInteger("490b5de7a287c59cefe267441a186ec24f63210fbf28877305f5896eaec5a7245d304ecb2b09d91066e627d7b2c8bf9e5271d882361a435355d1c2d1ac9d3069877189a01d64b2dd73a569e9e96b9a99767dbc02e04c839b09444f48430b113c1827c20b684ae33f5018051169f5acf4913ebd76a205c6f1aa2cc75747687d56", 16).toByteArray()); - String x_string = "9665839759b4fb9684e7438daecbd6e7129b4ebd3e4a107916e9a64bbbf399c9"; - expected_x = trim(new BigInteger(x_string, 16).toByteArray()); - assertEquals(new BigInteger(1, expected_x).toString(16), x_string); - a = "a72111a2"; - B = "6574ddce3e33c44a77198fa8b3656627e4a24c8786948e79f0c2588febaa485c94b1deb5e420bd3b46f9a34c7862525452ca7a0542c52f939d9f277a013aeceef7353a7741440f6dd2f6c2f1dc07fa5ca003e305c89c876a3035bd04f546b711d44da06a3ba827bc8affbf9ed46de1bfbc670ef9ed7c0bb8cdc588285d13849e"; - expected_M1 = trim(new BigInteger("03bbcf57aeaec89a3a254bb9650a924ea86aa0fdd83fd7274a75b7083f221cf0", 16).toByteArray()); - expected_M2 = trim(new BigInteger("082cf49ad5a34cc5ca571e3d063aec4bd96e7b96a6d951295180631650a84587", 16).toByteArray()); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - - x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); - assertTrue(Arrays.equals(x, expected_x)); - assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); - - A = client.exponential(); - assertTrue(Arrays.equals(A, expected_A)); - - M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - verified = client.verify(expected_M2); - assertTrue(verified); - - /* Test 7: password with ! */ - username = "parmegvtest3"; - password = "holahola!"; - salt = "1bf48d42b9a7ed32"; - expected_A = trim(new BigInteger("4e2fbe8db5d07f33ff1f4303959b5396dcffc1460b6ce8866fd388415f27fe10f9042986ab8682cdcf9a033e651bca89173688989adad854c91bc1918f98d5c82525fb6f328a8cf74ce1436b23821cba5337aaa20a3e5631e4b957053d542f2b5fc456e888371c9d6b94360b37adb2793eca8db100c24887c459e36d729a98e1", 16).toByteArray()); - x_string = "363d1d62dda07b2d987a9739ddb5ec32fcad9c7322fb64e87937f2da86c45d9f"; - expected_x = trim(new BigInteger(x_string, 16).toByteArray()); - assertEquals(new BigInteger(1, expected_x).toString(16), x_string); - a = "16dd0cf5"; - B = "dd5c9c5e13eb5daa6e7303928b3c826cec520ccef429c0dcb785be34c330d5bb89c99d7d94842b6b5c19cac600f884c50b26989b105f397115df7f3d13c5c7c2f6327cc547fc854ae40f09f1f6a104968bd510243feb104eb559e085fe1d720770be2887a1f424c534a3ab962d82e92458f652328bcf9878f95fdcf463d06193"; - expected_M1 = trim(new BigInteger("a7ffbff753a547b877f8944339b707b3ce1998da27badf253d56bf39f35308a6", 16).toByteArray()); - expected_M2 = trim(new BigInteger("5cc3d7f0077e978c83acdef14a725af01488c1728f0cf32cd7013d24faf5d901", 16).toByteArray()); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - - x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); - assertTrue(Arrays.equals(x, expected_x)); - assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); - - A = client.exponential(); - assertTrue(Arrays.equals(A, expected_A)); - - M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - verified = client.verify(expected_M2); - assertTrue(verified); - - /* Test 8: username and password *was* failing in localhost testProviderAPI*/ - username = "gg"; - password = "password"; - a = "bc925bfb"; - salt = "ff9ebb44e947cf59"; - expected_A = trim(new BigInteger("8f434633414faeaf035a0dea8c1cb7876bb1f8ee80d6fee8ea43ae60c4f9658550d825c25f1ed5c6a5543358bbcb559b76958c8047a2e7e5fe0072bc1f16401bcfa77b57651ff50dd665c6f28c302b37c98495eff397a56befead2e5ceffaace45f2ec200520258adb66df751e815e464656d869454e360d98cbc70f9c64fd4c", 16).toByteArray()); - x_string = "9cad2eca264380dd0b48e3b405e109c1be0615ee6ec92e7105eff5bc3a309fd9"; - expected_x = trim(new BigInteger(x_string, 16).toByteArray()); - assertEquals(new BigInteger(1, expected_x).toString(16), x_string); - B = "9ca2cd50b4c41047e4aa9e4fac9078ae21175e51e04a23877d6c2044765e39959e9a6a3ede99d08a556c196f51a2be12117681b1ef9d0b0498fb2fa4e88649ab9403e743504e3aaefbce8c5cb474eef8f4724ccd076fd33857de510d8509b67f166d986443bc262d776ec20985f617a7aa86e490290ce5d66332c8b45742a527"; - expected_M1 = trim(new BigInteger("7a2f768791abaeb954eb7f001bb60d91e6f61e959c8fcdefb58de857af9edaac", 16).toByteArray()); - expected_M2 = trim(new BigInteger("d78da7e0a23c9b87a2f09cdee05c510c105b4a8d471b47402c38f4cdfa49fe6d", 16).toByteArray()); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - - x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); - assertTrue(Arrays.equals(x, expected_x)); - assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); - - A = client.exponential(); - assertTrue(Arrays.equals(A, expected_A)); - - M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - verified = client.verify(expected_M2); - assertTrue(verified); - - /* Test 9: username and password *was* failing in localhost testProviderAPI*/ - username = "oo"; - password = "password"; - a = "1322ec50"; - salt = "a93c74934dcadd90"; - expected_A = trim(new BigInteger("c8e9f30a2f67977ee7e61e9ca5af8bd854b6cc98fe01dbe1b1a4cf002c1e2523b7e49f373a600ff85a84867817ec60fec532857812a07f5c6189f6172f133023af75ca4cb98b758bb84620d0aa3cfc74dc69e0507114c0aeab5a75c3ae3f07a919c5729420f03266c26ed41d1846e07de023ec68dd6830e9ebf129cf51abb571", 16).toByteArray()); - x_string = "20470538560c4beb4908e6bfe5b0e00da94223e361302a25c898cbdd3724020"; - expected_x = trim(new BigInteger(x_string, 16).toByteArray()); - assertEquals(new BigInteger(1, expected_x).toString(16), x_string); - B = "24f98ede155212bea8b1d8bacf8153735ee8114faa824c57c84df55f8d6072ab87f5ae885ce1062939dbaa68ca6e63147c1d2dc1f751e8be20d8a6f87287a2a83fcb1dc9b85dd406d438aeee5ccbc873603cb399627e26e6444e94b3d5d26764e866776c8960fe206bd33febeca9f55f6291dd2cb832eab69e5373f548adeefb"; - expected_M1 = trim(new BigInteger("1b35c705e563bd5239cdccc6627aa877c3023286f49b4b7c21341d2949ca2d15", 16).toByteArray()); - expected_M2 = trim(new BigInteger("a382025452bad8a6ccd0f703253fda90e7ea7bd0c2d466a389455080a4bd015d", 16).toByteArray()); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - - x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); - assertTrue(Arrays.equals(x, expected_x)); - assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); - - A = client.exponential(); - assertTrue(Arrays.equals(A, expected_A)); - - M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - verified = client.verify(expected_M2); - assertTrue(verified); - - /* Test 10: derived from test 11, discovered that password bytes should be in ISO-8859-1*/ - username = "nostradamus"; - password = "$[[//jjiilajfewahug43a89y¿"; - a = "800f0819"; - salt = "cfb9ae3ec5433076"; - expected_A = trim(new BigInteger("2ab09ee2fa01058f2f72fd2142b129f2ec26313801052889bcc4af57ee2e4d5b92c90cdfd6ecd660e82c635b2a091ba1b164e5b371c911ce0c4e69686baa120c58e2e0af84b2adc10da6cdfb0b579a1685032c57fd6ed1306d9713a562eddf5c833725042e825fa1abc7017f74760cb53d8c755ffe628c510022c296d1cd3584", 16).toByteArray()); - x_string = "9736a5e386a18f35bb08cac0f7c70bdbe120f2efe019874d0eb23b85b1955858"; - expected_x = trim(new BigInteger(x_string, 16).toByteArray()); - assertEquals(new BigInteger(1, expected_x).toString(16), x_string); - B = "2d19fe17dca1bda01044a0f406547895c32a10df2b0e69676de911273a8685d294763c4d16b3663f722b8980126e2c659efd33ffc6435a9594a2539e726c48e365893b3374670bd1958c13f55c2defa8ea9c0f9ba1345a5dca0e78debba434c8b755353d066d42bc5dfe0403fdcacfe5efd25c685f883ee6766c710b775c50f2"; - expected_M1 = trim(new BigInteger("a33feada1771c6f53e9343f5b9e69d51d4f15043c95fb663b6dd5b1c7af6f66b", 16).toByteArray()); - expected_M2 = trim(new BigInteger("9e99f9adfbfaa7add3626ed6e6aea94c9fa60dab6b8d56ad0cc950548f577d32", 16).toByteArray()); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - - x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); - assertTrue(Arrays.equals(x, expected_x)); - assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); - - A = client.exponential(); - assertTrue(Arrays.equals(A, expected_A)); - - M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - verified = client.verify(expected_M2); - assertTrue(verified); - - /* Test 11: username and password failing in localhost testProviderAPI*/ - username = "nostradamus"; - password = "$[['//\"jjiilajfewahug43a89y¿"; - a = "5bfbc968"; - salt = "1bcae1065951bbf5"; - expected_A = trim(new BigInteger("7a74c254d46dd6010a7090e574817a03f32ba13f98ed3c695d96f09c9d334e591771541400e68b6d27a19e734baccf3965ca79c0294ffbf553716b41fbca627c7cd3ea4a0d1c640c22411881696f59ad7ed8ce6ef7010e43f57fb3858aa4c3479dd41e4073afadb6a516c41f649b8cf30dea6366efa711c5106c83ea71b00da4", 16).toByteArray()); - x_string = "9834210874c883db35785ee6648079e13d22450c472d6469192ea775ff50c646"; - expected_x = trim(new BigInteger(x_string, 16).toByteArray()); - assertEquals(new BigInteger(1, expected_x).toString(16), x_string); - B = "285b00c034da5676dd8938ce6a7b717968fef2e5f479ecca6d95828a6ce809dd37893752c956245b5d13315987c50e57cc68aa4f770ff9ce977ddfd65052f278b90545286cf32b3d18307140514e0fe2269fc0437fb16104358f6fa127dc97281a017582759644862d736f48025f2b35cb1662067c11f2fcf0753e2f72c9e028"; - expected_M1 = trim(new BigInteger("fedbaff9d9a19efc4eea949b045297a6a3121cf371e2acdda85a2a1ca61c929d", 16).toByteArray()); - expected_M2 = trim(new BigInteger("ffccafa0febc1771a428082b30b7ce409856de4581c7d7d986f5b80015aba0d3", 16).toByteArray()); - - a_byte = new BigInteger(a, 16).toByteArray(); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - client = new LeapSRPSession(username, password, params, a_byte); - - x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); - assertTrue(Arrays.equals(x, expected_x)); - assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); - - A = client.exponential(); - assertTrue(Arrays.equals(A, expected_A)); - - M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); - - assertTrue(Arrays.equals(M1, expected_M1)); - - verified = client.verify(expected_M2); - assertTrue(verified); - } - - public 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; - } - -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index c1f13ba6..00000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/assets/minivpn.armeabi b/app/src/main/assets/minivpn.armeabi deleted file mode 100644 index 7018dbc9..00000000 Binary files a/app/src/main/assets/minivpn.armeabi and /dev/null differ diff --git a/app/src/main/assets/minivpn.armeabi-v7a b/app/src/main/assets/minivpn.armeabi-v7a deleted file mode 100644 index a8e01017..00000000 Binary files a/app/src/main/assets/minivpn.armeabi-v7a and /dev/null differ diff --git a/app/src/main/assets/minivpn.mips b/app/src/main/assets/minivpn.mips deleted file mode 100644 index c44e56c5..00000000 Binary files a/app/src/main/assets/minivpn.mips and /dev/null differ diff --git a/app/src/main/assets/minivpn.x86 b/app/src/main/assets/minivpn.x86 deleted file mode 100644 index 1a6bf464..00000000 Binary files a/app/src/main/assets/minivpn.x86 and /dev/null differ diff --git a/app/src/main/assets/urls/bitmask demo.url b/app/src/main/assets/urls/bitmask demo.url deleted file mode 100644 index 1a412055..00000000 --- a/app/src/main/assets/urls/bitmask demo.url +++ /dev/null @@ -1,3 +0,0 @@ -{ - "main_url" : "https://demo.bitmask.net/" -} diff --git a/app/src/main/java/org/jboss/security/srp/SRPParameters.java b/app/src/main/java/org/jboss/security/srp/SRPParameters.java deleted file mode 100644 index 4b188cb3..00000000 --- a/app/src/main/java/org/jboss/security/srp/SRPParameters.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * Copyright 2006, Red Hat Middleware LLC, and individual contributors - * as indicated by the @author tags. See the copyright.txt file in the - * distribution for a full listing of individual contributors. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ -package org.jboss.security.srp; - -import java.io.Serializable; -import java.util.Arrays; - -import org.spongycastle.util.encoders.Base64; - -/** The RFC2945 algorithm session parameters that the client and server -agree to use. In addition to the base RFC2945 parameters, one can choose an -alternate hash algorithm for the private session key. - -@author Scott.Stark@jboss.org -@version $Revision: 57210 $ -*/ -public class SRPParameters implements Cloneable, Serializable -{ - /** The serial version ID. - * @since 1.2.4.1 - */ - private static final long serialVersionUID = 6438772808805276693L; - - /** The algorithm safe-prime modulus */ - public final byte[] N; - /** The algorithm primitive generator */ - public final byte[] g; - /** The random password salt originally used to verify the password */ - public final byte[] s; - /** The algorithm to hash the session key to produce K. To be consistent - with the RFC2945 description this must be SHA_Interleave as implemented - by the JBossSX security provider. For compatibility with earlier JBossSX - SRP releases the algorithm must be SHA_ReverseInterleave. This name is - passed to java.security.MessageDigest.getInstance(). */ - public final String hashAlgorithm; - /** The algorithm to use for any encryption of data. - */ - public final String cipherAlgorithm; - /** The cipher intialization vector bytes - */ - public byte[] cipherIV; - - /** Creates a new instance of SRPParameters */ - public SRPParameters(byte[] N, byte[] g, byte[] s) - { - this(N, g, s, "SHA_Interleave", null); - } - public SRPParameters(byte[] N, byte[] g, byte[] s, String hashAlgorithm) - { - this(N, g, s, hashAlgorithm, null); - } - public SRPParameters(byte[] N, byte[] g, byte[] s, String hashAlgorithm, - String cipherAlgorithm) - { - this(N, g, s, hashAlgorithm, cipherAlgorithm, null); - } - public SRPParameters(byte[] N, byte[] g, byte[] s, String hashAlgorithm, - String cipherAlgorithm, byte[] cipherIV) - { - this.N = N; - this.g = g; - this.s = s; - if( hashAlgorithm == null ) - hashAlgorithm = "SHA_Interleave"; - this.hashAlgorithm = hashAlgorithm; - this.cipherAlgorithm = cipherAlgorithm; - this.cipherIV = cipherIV; - } - - public Object clone() - { - Object clone = null; - try - { - clone = super.clone(); - } - catch(CloneNotSupportedException e) - { - } - return clone; - } - - public int hashCode() - { - int hashCode = hashAlgorithm.hashCode(); - for(int i = 0; i < N.length; i ++) - hashCode += N[i]; - for(int i = 0; i < g.length; i ++) - hashCode += g[i]; - for(int i = 0; i < s.length; i ++) - hashCode += s[i]; - return hashCode; - } - - public boolean equals(Object obj) - { - boolean equals = false; - if( obj instanceof SRPParameters ) - { - SRPParameters p = (SRPParameters) obj; - equals = hashAlgorithm.equals(p.hashAlgorithm); - if( equals == true ) - equals = Arrays.equals(N, p.N); - if( equals == true ) - equals = Arrays.equals(g, p.g); - if( equals == true ) - equals = Arrays.equals(s, p.s); - } - return equals; - } - - public String toString() - { - StringBuffer tmp = new StringBuffer(super.toString()); - tmp.append('{'); - tmp.append("N: "); - tmp.append(Base64.encode(N)); - tmp.append("|g: "); - tmp.append(Base64.encode(g)); - tmp.append("|s: "); - tmp.append(Base64.encode(s)); - tmp.append("|hashAlgorithm: "); - tmp.append(hashAlgorithm); - tmp.append("|cipherAlgorithm: "); - tmp.append(cipherAlgorithm); - tmp.append("|cipherIV: "); - tmp.append(cipherIV); - tmp.append('}'); - return tmp.toString(); - } -} diff --git a/app/src/main/java/org/spongycastle/util/encoders/Base64.java b/app/src/main/java/org/spongycastle/util/encoders/Base64.java deleted file mode 100644 index 87bd80a0..00000000 --- a/app/src/main/java/org/spongycastle/util/encoders/Base64.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.spongycastle.util.encoders; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -public class Base64 -{ - private static final Encoder encoder = new Base64Encoder(); - - /** - * encode the input data producing a base 64 encoded byte array. - * - * @return a byte array containing the base 64 encoded data. - */ - public static byte[] encode( - byte[] data) - { - int len = (data.length + 2) / 3 * 4; - ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); - - try - { - encoder.encode(data, 0, data.length, bOut); - } - catch (IOException e) - { - throw new RuntimeException("exception encoding base64 string: " + e); - } - - return bOut.toByteArray(); - } - - /** - * Encode the byte data to base 64 writing it to the given output stream. - * - * @return the number of bytes produced. - */ - public static int encode( - byte[] data, - OutputStream out) - throws IOException - { - return encoder.encode(data, 0, data.length, out); - } - - /** - * Encode the byte data to base 64 writing it to the given output stream. - * - * @return the number of bytes produced. - */ - public static int encode( - byte[] data, - int off, - int length, - OutputStream out) - throws IOException - { - return encoder.encode(data, off, length, out); - } - - /** - * decode the base 64 encoded input data. It is assumed the input data is valid. - * - * @return a byte array representing the decoded data. - */ - public static byte[] decode( - byte[] data) - { - int len = data.length / 4 * 3; - ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); - - try - { - encoder.decode(data, 0, data.length, bOut); - } - catch (IOException e) - { - throw new RuntimeException("exception decoding base64 string: " + e); - } - - return bOut.toByteArray(); - } - - /** - * decode the base 64 encoded String data - whitespace will be ignored. - * - * @return a byte array representing the decoded data. - */ - public static byte[] decode( - String data) - { - int len = data.length() / 4 * 3; - ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); - - try - { - encoder.decode(data, bOut); - } - catch (IOException e) - { - throw new RuntimeException("exception decoding base64 string: " + e); - } - - return bOut.toByteArray(); - } - - /** - * decode the base 64 encoded String data writing it to the given output stream, - * whitespace characters will be ignored. - * - * @return the number of bytes produced. - */ - public static int decode( - String data, - OutputStream out) - throws IOException - { - return encoder.decode(data, out); - } -} diff --git a/app/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java b/app/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java deleted file mode 100644 index 84060707..00000000 --- a/app/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java +++ /dev/null @@ -1,298 +0,0 @@ -package org.spongycastle.util.encoders; - -import java.io.IOException; -import java.io.OutputStream; - -public class Base64Encoder - implements Encoder -{ - protected final byte[] encodingTable = - { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', - (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', - (byte)'7', (byte)'8', (byte)'9', - (byte)'+', (byte)'/' - }; - - protected byte padding = (byte)'='; - - /* - * set up the decoding table. - */ - protected final byte[] decodingTable = new byte[128]; - - protected void initialiseDecodingTable() - { - for (int i = 0; i < encodingTable.length; i++) - { - decodingTable[encodingTable[i]] = (byte)i; - } - } - - public Base64Encoder() - { - initialiseDecodingTable(); - } - - /** - * encode the input data producing a base 64 output stream. - * - * @return the number of bytes produced. - */ - public int encode( - byte[] data, - int off, - int length, - OutputStream out) - throws IOException - { - int modulus = length % 3; - int dataLength = (length - modulus); - int a1, a2, a3; - - for (int i = off; i < off + dataLength; i += 3) - { - a1 = data[i] & 0xff; - a2 = data[i + 1] & 0xff; - a3 = data[i + 2] & 0xff; - - out.write(encodingTable[(a1 >>> 2) & 0x3f]); - out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]); - out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]); - out.write(encodingTable[a3 & 0x3f]); - } - - /* - * process the tail end. - */ - int b1, b2, b3; - int d1, d2; - - switch (modulus) - { - case 0: /* nothing left to do */ - break; - case 1: - d1 = data[off + dataLength] & 0xff; - b1 = (d1 >>> 2) & 0x3f; - b2 = (d1 << 4) & 0x3f; - - out.write(encodingTable[b1]); - out.write(encodingTable[b2]); - out.write(padding); - out.write(padding); - break; - case 2: - d1 = data[off + dataLength] & 0xff; - d2 = data[off + dataLength + 1] & 0xff; - - b1 = (d1 >>> 2) & 0x3f; - b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; - b3 = (d2 << 2) & 0x3f; - - out.write(encodingTable[b1]); - out.write(encodingTable[b2]); - out.write(encodingTable[b3]); - out.write(padding); - break; - } - - return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4); - } - - private boolean ignore( - char c) - { - return (c == '\n' || c =='\r' || c == '\t' || c == ' '); - } - - /** - * decode the base 64 encoded byte data writing it to the given output stream, - * whitespace characters will be ignored. - * - * @return the number of bytes produced. - */ - public int decode( - byte[] data, - int off, - int length, - OutputStream out) - throws IOException - { - byte b1, b2, b3, b4; - int outLen = 0; - - int end = off + length; - - while (end > off) - { - if (!ignore((char)data[end - 1])) - { - break; - } - - end--; - } - - int i = off; - int finish = end - 4; - - i = nextI(data, i, finish); - - while (i < finish) - { - b1 = decodingTable[data[i++]]; - - i = nextI(data, i, finish); - - b2 = decodingTable[data[i++]]; - - i = nextI(data, i, finish); - - b3 = decodingTable[data[i++]]; - - i = nextI(data, i, finish); - - b4 = decodingTable[data[i++]]; - - out.write((b1 << 2) | (b2 >> 4)); - out.write((b2 << 4) | (b3 >> 2)); - out.write((b3 << 6) | b4); - - outLen += 3; - - i = nextI(data, i, finish); - } - - outLen += decodeLastBlock(out, (char)data[end - 4], (char)data[end - 3], (char)data[end - 2], (char)data[end - 1]); - - return outLen; - } - - private int nextI(byte[] data, int i, int finish) - { - while ((i < finish) && ignore((char)data[i])) - { - i++; - } - return i; - } - - /** - * decode the base 64 encoded String data writing it to the given output stream, - * whitespace characters will be ignored. - * - * @return the number of bytes produced. - */ - public int decode( - String data, - OutputStream out) - throws IOException - { - byte b1, b2, b3, b4; - int length = 0; - - int end = data.length(); - - while (end > 0) - { - if (!ignore(data.charAt(end - 1))) - { - break; - } - - end--; - } - - int i = 0; - int finish = end - 4; - - i = nextI(data, i, finish); - - while (i < finish) - { - b1 = decodingTable[data.charAt(i++)]; - - i = nextI(data, i, finish); - - b2 = decodingTable[data.charAt(i++)]; - - i = nextI(data, i, finish); - - b3 = decodingTable[data.charAt(i++)]; - - i = nextI(data, i, finish); - - b4 = decodingTable[data.charAt(i++)]; - - out.write((b1 << 2) | (b2 >> 4)); - out.write((b2 << 4) | (b3 >> 2)); - out.write((b3 << 6) | b4); - - length += 3; - - i = nextI(data, i, finish); - } - - length += decodeLastBlock(out, data.charAt(end - 4), data.charAt(end - 3), data.charAt(end - 2), data.charAt(end - 1)); - - return length; - } - - private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4) - throws IOException - { - byte b1, b2, b3, b4; - - if (c3 == padding) - { - b1 = decodingTable[c1]; - b2 = decodingTable[c2]; - - out.write((b1 << 2) | (b2 >> 4)); - - return 1; - } - else if (c4 == padding) - { - b1 = decodingTable[c1]; - b2 = decodingTable[c2]; - b3 = decodingTable[c3]; - - out.write((b1 << 2) | (b2 >> 4)); - out.write((b2 << 4) | (b3 >> 2)); - - return 2; - } - else - { - b1 = decodingTable[c1]; - b2 = decodingTable[c2]; - b3 = decodingTable[c3]; - b4 = decodingTable[c4]; - - out.write((b1 << 2) | (b2 >> 4)); - out.write((b2 << 4) | (b3 >> 2)); - out.write((b3 << 6) | b4); - - return 3; - } - } - - private int nextI(String data, int i, int finish) - { - while ((i < finish) && ignore(data.charAt(i))) - { - i++; - } - return i; - } -} diff --git a/app/src/main/java/org/spongycastle/util/encoders/Encoder.java b/app/src/main/java/org/spongycastle/util/encoders/Encoder.java deleted file mode 100644 index 106c36b7..00000000 --- a/app/src/main/java/org/spongycastle/util/encoders/Encoder.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.spongycastle.util.encoders; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * Encode and decode byte arrays (typically from binary to 7-bit ASCII - * encodings). - */ -public interface Encoder -{ - int encode(byte[] data, int off, int length, OutputStream out) throws IOException; - - int decode(byte[] data, int off, int length, OutputStream out) throws IOException; - - int decode(String data, OutputStream out) throws IOException; -} diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java b/app/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java deleted file mode 100644 index 0127ca0c..00000000 --- a/app/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.spongycastle.util.io.pem; - -import java.io.IOException; - -@SuppressWarnings("serial") -public class PemGenerationException - extends IOException -{ - private Throwable cause; - - public PemGenerationException(String message, Throwable cause) - { - super(message); - this.cause = cause; - } - - public PemGenerationException(String message) - { - super(message); - } - - public Throwable getCause() - { - return cause; - } -} diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemHeader.java b/app/src/main/java/org/spongycastle/util/io/pem/PemHeader.java deleted file mode 100644 index 4adb815e..00000000 --- a/app/src/main/java/org/spongycastle/util/io/pem/PemHeader.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.spongycastle.util.io.pem; - -public class PemHeader -{ - private String name; - private String value; - - public PemHeader(String name, String value) - { - this.name = name; - this.value = value; - } - - public String getName() - { - return name; - } - - public String getValue() - { - return value; - } - - public int hashCode() - { - return getHashCode(this.name) + 31 * getHashCode(this.value); - } - - public boolean equals(Object o) - { - if (!(o instanceof PemHeader)) - { - return false; - } - - PemHeader other = (PemHeader)o; - - return other == this || (isEqual(this.name, other.name) && isEqual(this.value, other.value)); - } - - private int getHashCode(String s) - { - if (s == null) - { - return 1; - } - - return s.hashCode(); - } - - private boolean isEqual(String s1, String s2) - { - if (s1 == s2) - { - return true; - } - - if (s1 == null || s2 == null) - { - return false; - } - - return s1.equals(s2); - } - -} diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemObject.java b/app/src/main/java/org/spongycastle/util/io/pem/PemObject.java deleted file mode 100644 index 6f7c79c5..00000000 --- a/app/src/main/java/org/spongycastle/util/io/pem/PemObject.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.spongycastle.util.io.pem; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@SuppressWarnings("all") -public class PemObject - implements PemObjectGenerator -{ - private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList()); - - private String type; - private List headers; - private byte[] content; - - /** - * Generic constructor for object without headers. - * - * @param type pem object type. - * @param content the binary content of the object. - */ - public PemObject(String type, byte[] content) - { - this(type, EMPTY_LIST, content); - } - - /** - * Generic constructor for object with headers. - * - * @param type pem object type. - * @param headers a list of PemHeader objects. - * @param content the binary content of the object. - */ - public PemObject(String type, List headers, byte[] content) - { - this.type = type; - this.headers = Collections.unmodifiableList(headers); - this.content = content; - } - - public String getType() - { - return type; - } - - public List getHeaders() - { - return headers; - } - - public byte[] getContent() - { - return content; - } - - public PemObject generate() - throws PemGenerationException - { - return this; - } -} diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java b/app/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java deleted file mode 100644 index 1a8cea6d..00000000 --- a/app/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.spongycastle.util.io.pem; - -public interface PemObjectGenerator -{ - PemObject generate() - throws PemGenerationException; -} diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemWriter.java b/app/src/main/java/org/spongycastle/util/io/pem/PemWriter.java deleted file mode 100644 index f5a6a363..00000000 --- a/app/src/main/java/org/spongycastle/util/io/pem/PemWriter.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.spongycastle.util.io.pem; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.Writer; -import java.util.Iterator; - -import org.spongycastle.util.encoders.Base64; - -/** - * A generic PEM writer, based on RFC 1421 - */ -@SuppressWarnings("all") -public class PemWriter - extends BufferedWriter -{ - private static final int LINE_LENGTH = 64; - - private final int nlLength; - private char[] buf = new char[LINE_LENGTH]; - - /** - * Base constructor. - * - * @param out output stream to use. - */ - public PemWriter(Writer out) - { - super(out); - - String nl = System.getProperty("line.separator"); - if (nl != null) - { - nlLength = nl.length(); - } - else - { - nlLength = 2; - } - } - - /** - * Return the number of bytes or characters required to contain the - * passed in object if it is PEM encoded. - * - * @param obj pem object to be output - * @return an estimate of the number of bytes - */ - public int getOutputSize(PemObject obj) - { - // BEGIN and END boundaries. - int size = (2 * (obj.getType().length() + 10 + nlLength)) + 6 + 4; - - if (!obj.getHeaders().isEmpty()) - { - for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) - { - PemHeader hdr = (PemHeader)it.next(); - - size += hdr.getName().length() + ": ".length() + hdr.getValue().length() + nlLength; - } - - size += nlLength; - } - - // base64 encoding - int dataLen = ((obj.getContent().length + 2) / 3) * 4; - - size += dataLen + (((dataLen + LINE_LENGTH - 1) / LINE_LENGTH) * nlLength); - - return size; - } - - public void writeObject(PemObjectGenerator objGen) - throws IOException - { - PemObject obj = objGen.generate(); - - writePreEncapsulationBoundary(obj.getType()); - - if (!obj.getHeaders().isEmpty()) - { - for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) - { - PemHeader hdr = (PemHeader)it.next(); - - this.write(hdr.getName()); - this.write(": "); - this.write(hdr.getValue()); - this.newLine(); - } - - this.newLine(); - } - - writeEncoded(obj.getContent()); - writePostEncapsulationBoundary(obj.getType()); - } - - private void writeEncoded(byte[] bytes) - throws IOException - { - bytes = Base64.encode(bytes); - - for (int i = 0; i < bytes.length; i += buf.length) - { - int index = 0; - - while (index != buf.length) - { - if ((i + index) >= bytes.length) - { - break; - } - buf[index] = (char)bytes[i + index]; - index++; - } - this.write(buf, 0, index); - this.newLine(); - } - } - - private void writePreEncapsulationBoundary( - String type) - throws IOException - { - this.write("-----BEGIN " + type + "-----"); - this.newLine(); - } - - private void writePostEncapsulationBoundary( - String type) - throws IOException - { - this.write("-----END " + type + "-----"); - this.newLine(); - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java b/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java deleted file mode 100644 index 6d025422..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java +++ /dev/null @@ -1,40 +0,0 @@ -package se.leap.bitmaskclient; - -import android.app.Activity; -import android.app.Fragment; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import se.leap.bitmaskclient.R; - -public class AboutActivity extends Activity { - - final public static String TAG = "aboutFragment"; - final public static int VIEWED = 0; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.about); - TextView ver = (TextView) findViewById(R.id.version); - - String version; - String name="Openvpn"; - try { - PackageInfo packageinfo = getPackageManager().getPackageInfo(getPackageName(), 0); - version = packageinfo.versionName; - name = getString(R.string.app); - } catch (NameNotFoundException e) { - version = "error fetching version"; - } - - - ver.setText(getString(R.string.version_info,name,version)); - setResult(VIEWED); - } - -} diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java deleted file mode 100644 index a8bd3b7a..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package se.leap.bitmaskclient; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.io.InputStream; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -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 org.json.JSONException; -import org.json.JSONObject; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Base64; - -/** - * Stores constants, and implements auxiliary methods used across all LEAP Android classes. - * - * @author parmegv - * @author MeanderingCode - * - */ -public class ConfigHelper { - 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 downloaded_string) { - try { - if(new JSONObject(downloaded_string).has(ProviderAPI.ERRORS) || downloaded_string.isEmpty()) { - return true; - } else { - return false; - } - } catch(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 certificate_string) { - java.security.cert.Certificate certificate = null; - CertificateFactory cf; - try { - cf = CertificateFactory.getInstance("X.509"); - - certificate_string = certificate_string.replaceFirst("-----BEGIN CERTIFICATE-----", "").replaceFirst("-----END CERTIFICATE-----", "").trim(); - byte[] cert_bytes = Base64.decode(certificate_string, Base64.DEFAULT); - InputStream caInput = new ByteArrayInputStream(cert_bytes); - try { - certificate = cf.generateCertificate(caInput); - System.out.println("ca=" + ((X509Certificate) certificate).getSubjectDN()); - } finally { - caInput.close(); - } - } catch (CertificateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - return null; - } - - return (X509Certificate) certificate; - } - - protected static RSAPrivateKey parseRsaKeyFromString(String RsaKeyString) { - RSAPrivateKey key = null; - 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, Base64.DEFAULT) ); - 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; - } - - return key; - } - - /** - * Adds a new X509 certificate given its input stream and its provider name - * @param provider used to store the certificate in the keystore - * @param inputStream from which X509 certificate must be generated. - */ - public static void addTrustedCertificate(String provider, InputStream inputStream) { - CertificateFactory cf; - try { - cf = CertificateFactory.getInstance("X.509"); - X509Certificate cert = - (X509Certificate)cf.generateCertificate(inputStream); - keystore_trusted.setCertificateEntry(provider, cert); - } catch (CertificateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (KeyStoreException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - /** - * Adds a new X509 certificate given in its string from and using its provider name - * @param provider used to store the certificate in the keystore - * @param certificate - */ - public static void addTrustedCertificate(String provider, String certificate) { - - try { - X509Certificate cert = ConfigHelper.parseX509CertificateFromString(certificate); - if(keystore_trusted == null) { - keystore_trusted = KeyStore.getInstance("BKS"); - keystore_trusted.load(null); - } - keystore_trusted.setCertificateEntry(provider, cert); - } catch (KeyStoreException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (NoSuchAlgorithmException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (CertificateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - /** - * @return class wide keystore - */ - public static KeyStore getKeystore() { - return keystore_trusted; - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigurationWizard.java b/app/src/main/java/se/leap/bitmaskclient/ConfigurationWizard.java deleted file mode 100644 index f0aac40b..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/ConfigurationWizard.java +++ /dev/null @@ -1,581 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package se.leap.bitmaskclient; - - - - - - -import android.app.Activity; -import android.app.DialogFragment; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.res.AssetManager; -import android.os.Bundle; -import android.os.Handler; -import android.util.Log; -import android.view.Display; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View.MeasureSpec; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.TextView; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Iterator; -import org.json.JSONException; -import org.json.JSONObject; -import se.leap.bitmaskclient.DownloadFailedDialog.DownloadFailedDialogInterface; -import se.leap.bitmaskclient.NewProviderDialog.NewProviderDialogInterface; -import se.leap.bitmaskclient.ProviderAPIResultReceiver.Receiver; -import se.leap.bitmaskclient.ProviderDetailFragment.ProviderDetailFragmentInterface; -import se.leap.bitmaskclient.ProviderListContent.ProviderItem; -import se.leap.bitmaskclient.R; - -/** - * Activity that builds and shows the list of known available providers. - * - * It also allows the user to enter custom providers with a button. - * - * @author parmegv - * - */ -public class ConfigurationWizard extends Activity -implements ProviderListFragment.Callbacks, NewProviderDialogInterface, ProviderDetailFragmentInterface, DownloadFailedDialogInterface, Receiver { - - private ProgressBar mProgressBar; - private TextView progressbar_description; - private ProviderListFragment provider_list_fragment; - private Intent mConfigState = new Intent(); - - final public static String TAG = "se.leap.bitmaskclient.ConfigurationWizard"; - final public static String TYPE_OF_CERTIFICATE = "type_of_certificate"; - final public static String ANON_CERTIFICATE = "anon_certificate"; - final public static String AUTHED_CERTIFICATE = "authed_certificate"; - - final protected static String PROVIDER_SET = "PROVIDER SET"; - final protected static String SERVICES_RETRIEVED = "SERVICES RETRIEVED"; - - public ProviderAPIResultReceiver providerAPI_result_receiver; - private ProviderAPIBroadcastReceiver_Update providerAPI_broadcast_receiver_update; - - private static SharedPreferences preferences; - private static boolean setting_up_provider = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - preferences = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE); - - setContentView(R.layout.configuration_wizard_activity); - mProgressBar = (ProgressBar) findViewById(R.id.progressbar_configuration_wizard); - mProgressBar.setVisibility(ProgressBar.INVISIBLE); - progressbar_description = (TextView) findViewById(R.id.progressbar_description); - progressbar_description.setVisibility(TextView.INVISIBLE); - providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); - providerAPI_result_receiver.setReceiver(this); - providerAPI_broadcast_receiver_update = new ProviderAPIBroadcastReceiver_Update(); - IntentFilter update_intent_filter = new IntentFilter(ProviderAPI.UPDATE_PROGRESSBAR); - update_intent_filter.addCategory(Intent.CATEGORY_DEFAULT); - registerReceiver(providerAPI_broadcast_receiver_update, update_intent_filter); - - loadPreseededProviders(); - - // Only create our fragments if we're not restoring a saved instance - if ( savedInstanceState == null ){ - // TODO Some welcome screen? - // We will need better flow control when we have more Fragments (e.g. user auth) - provider_list_fragment = ProviderListFragment.newInstance(); - Bundle arguments = new Bundle(); - int configuration_wizard_request_code = getIntent().getIntExtra(Dashboard.REQUEST_CODE, -1); - if(configuration_wizard_request_code == Dashboard.SWITCH_PROVIDER) { - arguments.putBoolean(ProviderListFragment.SHOW_ALL_PROVIDERS, true); - } - provider_list_fragment.setArguments(arguments); - - FragmentManager fragmentManager = getFragmentManager(); - fragmentManager.beginTransaction() - .replace(R.id.configuration_wizard_layout, provider_list_fragment, ProviderListFragment.TAG) - .commit(); - } - - // TODO: If exposing deep links into your app, handle intents here. - } - - @Override - protected void onDestroy() { - super.onDestroy(); - unregisterReceiver(providerAPI_broadcast_receiver_update); - } - - public void refreshProviderList(int top_padding) { - ProviderListFragment new_provider_list_fragment = new ProviderListFragment(); - Bundle top_padding_bundle = new Bundle(); - top_padding_bundle.putInt(ProviderListFragment.TOP_PADDING, top_padding); - new_provider_list_fragment.setArguments(top_padding_bundle); - - FragmentManager fragmentManager = getFragmentManager(); - fragmentManager.beginTransaction() - .replace(R.id.configuration_wizard_layout, new_provider_list_fragment, ProviderListFragment.TAG) - .commit(); - } - - @Override - public void onReceiveResult(int resultCode, Bundle resultData) { - if(resultCode == ProviderAPI.PROVIDER_OK) { - mConfigState.setAction(PROVIDER_SET); - - if (preferences.getBoolean(EIP.ALLOWED_ANON, false)){ - mConfigState.putExtra(SERVICES_RETRIEVED, true); - downloadAnonCert(); - } else { - mProgressBar.incrementProgressBy(1); - mProgressBar.setVisibility(ProgressBar.GONE); - progressbar_description.setVisibility(TextView.GONE); - setResult(RESULT_OK); - showProviderDetails(getCurrentFocus()); - } - } else if(resultCode == ProviderAPI.PROVIDER_NOK) { - //refreshProviderList(0); - String reason_to_fail = resultData.getString(ProviderAPI.ERRORS); - showDownloadFailedDialog(getCurrentFocus(), reason_to_fail); - mProgressBar.setVisibility(ProgressBar.GONE); - progressbar_description.setVisibility(TextView.GONE); - preferences.edit().remove(Provider.KEY).commit(); - setting_up_provider = false; - setResult(RESULT_CANCELED, mConfigState); - } - else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { - mProgressBar.incrementProgressBy(1); - mProgressBar.setVisibility(ProgressBar.GONE); - progressbar_description.setVisibility(TextView.GONE); - //refreshProviderList(0); - setResult(RESULT_OK); - showProviderDetails(getCurrentFocus()); - } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { - //refreshProviderList(0); - mProgressBar.setVisibility(ProgressBar.GONE); - progressbar_description.setVisibility(TextView.GONE); - //Toast.makeText(getApplicationContext(), R.string.incorrectly_downloaded_certificate_message, Toast.LENGTH_LONG).show(); - setResult(RESULT_CANCELED, mConfigState); - } else if(resultCode == AboutActivity.VIEWED) { - // Do nothing, right now - // I need this for CW to wait for the About activity to end before going back to Dashboard. - } - } - - /** - * Callback method from {@link ProviderListFragment.Callbacks} - * indicating that the item with the given ID was selected. - */ - @Override - public void onItemSelected(String id) { - //TODO Code 2 pane view - // resetOldConnection(); - ProviderItem selected_provider = getProvider(id); - int provider_index = getProviderIndex(id); - - - startProgressBar(provider_index+1); - provider_list_fragment.hideAllBut(provider_index); - - boolean danger_on = true; - if(preferences.contains(ProviderItem.DANGER_ON)) - danger_on = preferences.getBoolean(ProviderItem.DANGER_ON, false); - setUpProvider(selected_provider.providerMainUrl(), danger_on); - } - - @Override - public void onBackPressed() { - if(setting_up_provider) { - stopSettingUpProvider(); - } else { - usualBackButton(); - } - } - - private void stopSettingUpProvider() { - ProviderAPI.stop(); - mProgressBar.setVisibility(ProgressBar.GONE); - mProgressBar.setProgress(0); - progressbar_description.setVisibility(TextView.GONE); - getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit().remove(Provider.KEY).commit(); - setting_up_provider = false; - showAllProviders(); - } - - private void usualBackButton() { - try { - boolean is_provider_set_up = new JSONObject(preferences.getString(Provider.KEY, "no provider")) != null ? true : false; - boolean is_provider_set_up_truly = new JSONObject(preferences.getString(Provider.KEY, "no provider")).length() != 0 ? true : false; - if(!is_provider_set_up || !is_provider_set_up_truly) { - askDashboardToQuitApp(); - } else { - setResult(RESULT_OK); - } - } catch (JSONException e) { - askDashboardToQuitApp(); - super.onBackPressed(); - e.printStackTrace(); - } - super.onBackPressed(); - } - private void askDashboardToQuitApp() { - Intent ask_quit = new Intent(); - ask_quit.putExtra(Dashboard.ACTION_QUIT, Dashboard.ACTION_QUIT); - setResult(RESULT_CANCELED, ask_quit); - } - - private ProviderItem getProvider(String name) { - Iterator providers_iterator = ProviderListContent.ITEMS.iterator(); - while(providers_iterator.hasNext()) { - ProviderItem provider = providers_iterator.next(); - if(provider.name().equalsIgnoreCase(name)) { - return provider; - } - } - return null; - } - - private String getId(String provider_main_url_string) { - try { - URL provider_url = new URL(provider_main_url_string); - URL aux_provider_url; - Iterator providers_iterator = ProviderListContent.ITEMS.iterator(); - while(providers_iterator.hasNext()) { - ProviderItem provider = providers_iterator.next(); - aux_provider_url = new URL(provider.providerMainUrl()); - if(isSameURL(provider_url, aux_provider_url)) { - return provider.name(); - } - } - } catch (MalformedURLException e) { - e.printStackTrace(); - } - return ""; - } - - /** - * Checks, whether 2 urls are pointing to the same location. - * - * @param url a url - * @param baseUrl an other url, that should be compared. - * @return true, if the urls point to the same host and port and use the - * same protocol, false otherwise. - */ - private boolean isSameURL(final URL url, final URL baseUrl) { - if (!url.getProtocol().equals(baseUrl.getProtocol())) { - return false; - } - if (!url.getHost().equals(baseUrl.getHost())) { - return false; - } - if (url.getPort() != baseUrl.getPort()) { - return false; - } - return true; - } - - private void startProgressBar() { - mProgressBar.setVisibility(ProgressBar.VISIBLE); - mProgressBar.setProgress(0); - mProgressBar.setMax(3); - } - - private void startProgressBar(int list_item_index) { - mProgressBar.setVisibility(ProgressBar.VISIBLE); - progressbar_description.setVisibility(TextView.VISIBLE); - mProgressBar.setProgress(0); - mProgressBar.setMax(3); - int measured_height = listItemHeight(list_item_index); - mProgressBar.setTranslationY(measured_height); - progressbar_description.setTranslationY(measured_height + mProgressBar.getHeight()); - } - - private int getProviderIndex(String id) { - int index = 0; - Iterator providers_iterator = ProviderListContent.ITEMS.iterator(); - while(providers_iterator.hasNext()) { - ProviderItem provider = providers_iterator.next(); - if(provider.name().equalsIgnoreCase(id)) { - break; - } else index++; - } - return index; - } - - private int listItemHeight(int list_item_index) { - ListView provider_list_view = (ListView)findViewById(android.R.id.list); - ListAdapter provider_list_adapter = provider_list_view.getAdapter(); - View listItem = provider_list_adapter.getView(0, null, provider_list_view); - listItem.setLayoutParams(new RelativeLayout.LayoutParams( - RelativeLayout.LayoutParams.WRAP_CONTENT, - RelativeLayout.LayoutParams.WRAP_CONTENT)); - WindowManager wm = (WindowManager) getApplicationContext() - .getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - int screenWidth = display.getWidth(); // deprecated - - int listViewWidth = screenWidth - 10 - 10; - int widthSpec = MeasureSpec.makeMeasureSpec(listViewWidth, - MeasureSpec.AT_MOST); - listItem.measure(widthSpec, 0); - - return listItem.getMeasuredHeight(); -} - - /** - * Loads providers data from url file contained in the project - * @return true if the file was read correctly - */ - private boolean loadPreseededProviders() { - boolean loaded_preseeded_providers = false; - AssetManager asset_manager = getAssets(); - String[] urls_filepaths = null; - try { - String url_files_folder = "urls"; - //TODO Put that folder in a better place (also inside the "for") - urls_filepaths = asset_manager.list(url_files_folder); - String provider_name = ""; - for(String url_filepath : urls_filepaths) - { - boolean custom = false; - provider_name = url_filepath.subSequence(0, url_filepath.indexOf(".")).toString(); - if(ProviderListContent.ITEMS.isEmpty()) //TODO I have to implement a way of checking if a provider new or is already present in that ITEMS list - ProviderListContent.addItem(new ProviderItem(provider_name, asset_manager.open(url_files_folder + "/" + url_filepath))); - loaded_preseeded_providers = true; - } - } catch (IOException e) { - loaded_preseeded_providers = false; - } - - return loaded_preseeded_providers; - } - - /** - * Asks ProviderAPI to download an anonymous (anon) VPN certificate. - */ - private void downloadAnonCert() { - Intent provider_API_command = new Intent(this, ProviderAPI.class); - - Bundle parameters = new Bundle(); - - parameters.putString(TYPE_OF_CERTIFICATE, ANON_CERTIFICATE); - - provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); - provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); - provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); - - startService(provider_API_command); - } - - /** - * Open the new provider dialog - */ - public void addAndSelectNewProvider() { - FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); - Fragment previous_new_provider_dialog = getFragmentManager().findFragmentByTag(NewProviderDialog.TAG); - if (previous_new_provider_dialog != null) { - fragment_transaction.remove(previous_new_provider_dialog); - } - fragment_transaction.addToBackStack(null); - - DialogFragment newFragment = NewProviderDialog.newInstance(); - newFragment.show(fragment_transaction, NewProviderDialog.TAG); - } - - /** - * Open the new provider dialog with data - */ - public void addAndSelectNewProvider(String main_url, boolean danger_on) { - FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); - Fragment previous_new_provider_dialog = getFragmentManager().findFragmentByTag(NewProviderDialog.TAG); - if (previous_new_provider_dialog != null) { - fragment_transaction.remove(previous_new_provider_dialog); - } - - DialogFragment newFragment = NewProviderDialog.newInstance(); - Bundle data = new Bundle(); - data.putString(Provider.MAIN_URL, main_url); - data.putBoolean(ProviderItem.DANGER_ON, danger_on); - newFragment.setArguments(data); - newFragment.show(fragment_transaction, NewProviderDialog.TAG); - } - - /** - * Once selected a provider, this fragment offers the user to log in, - * use it anonymously (if possible) - * or cancel his/her election pressing the back button. - * @param view - * @param reason_to_fail - */ - public void showDownloadFailedDialog(View view, String reason_to_fail) { - FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); - Fragment previous_provider_details_dialog = getFragmentManager().findFragmentByTag(DownloadFailedDialog.TAG); - if (previous_provider_details_dialog != null) { - fragment_transaction.remove(previous_provider_details_dialog); - } - fragment_transaction.addToBackStack(null); - - DialogFragment newFragment = DownloadFailedDialog.newInstance(reason_to_fail); - newFragment.show(fragment_transaction, DownloadFailedDialog.TAG); - } - - /** - * Once selected a provider, this fragment offers the user to log in, - * use it anonymously (if possible) - * or cancel his/her election pressing the back button. - * @param view - */ - public void showProviderDetails(View view) { - if(setting_up_provider) { - FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); - Fragment previous_provider_details_dialog = getFragmentManager().findFragmentByTag(ProviderDetailFragment.TAG); - if (previous_provider_details_dialog != null) { - fragment_transaction.remove(previous_provider_details_dialog); - } - fragment_transaction.addToBackStack(null); - - DialogFragment newFragment = ProviderDetailFragment.newInstance(); - newFragment.show(fragment_transaction, ProviderDetailFragment.TAG); - } - } - - public void showAndSelectProvider(String provider_main_url, boolean danger_on) { - if(getId(provider_main_url).isEmpty()) - showProvider(provider_main_url, danger_on); - autoSelectProvider(provider_main_url, danger_on); - } - - private void showProvider(final String provider_main_url, final boolean danger_on) { - String provider_name = provider_main_url.replaceFirst("http[s]?://", "").replaceFirst("\\/", "_"); - ProviderItem added_provider = new ProviderItem(provider_name, provider_main_url); - provider_list_fragment.addItem(added_provider); - } - - private void autoSelectProvider(String provider_main_url, boolean danger_on) { - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(ProviderItem.DANGER_ON, danger_on).commit(); - onItemSelected(getId(provider_main_url)); - } - - /** - * Asks ProviderAPI to download a new provider.json file - * @param provider_name - * @param provider_main_url - * @param danger_on tells if HTTPS client should bypass certificate errors - */ - public void setUpProvider(String provider_main_url, boolean danger_on) { - Intent provider_API_command = new Intent(this, ProviderAPI.class); - Bundle parameters = new Bundle(); - parameters.putString(Provider.MAIN_URL, provider_main_url); - parameters.putBoolean(ProviderItem.DANGER_ON, danger_on); - - provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); - provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); - provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); - - startService(provider_API_command); - setting_up_provider = true; - } - - public void retrySetUpProvider() { - cancelSettingUpProvider(); - if(!ProviderAPI.caCertDownloaded()) { - addAndSelectNewProvider(ProviderAPI.lastProviderMainUrl(), ProviderAPI.lastDangerOn()); - } else { - Intent provider_API_command = new Intent(this, ProviderAPI.class); - - provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); - provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); - - startService(provider_API_command); - } - } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.configuration_wizard_activity, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item){ - switch (item.getItemId()){ - case R.id.about_leap: - startActivityForResult(new Intent(this, AboutActivity.class), 0); - return true; - case R.id.new_provider: - addAndSelectNewProvider(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - public void showAllProviders() { - provider_list_fragment = (ProviderListFragment) getFragmentManager().findFragmentByTag(ProviderListFragment.TAG); - if(provider_list_fragment != null) - provider_list_fragment.unhideAll(); - } - - public void cancelSettingUpProvider() { - provider_list_fragment = (ProviderListFragment) getFragmentManager().findFragmentByTag(ProviderListFragment.TAG); - if(provider_list_fragment != null && preferences.contains(ProviderItem.DANGER_ON)) { - provider_list_fragment.removeLastItem(); - } - preferences.edit().remove(Provider.KEY).remove(ProviderItem.DANGER_ON).remove(EIP.ALLOWED_ANON).remove(EIP.KEY).commit(); - } - - @Override - public void login() { - Intent ask_login = new Intent(); - ask_login.putExtra(LogInDialog.VERB, LogInDialog.VERB); - setResult(RESULT_OK, ask_login); - setting_up_provider = false; - finish(); - } - - @Override - public void use_anonymously() { - setResult(RESULT_OK); - setting_up_provider = false; - finish(); - } - - public class ProviderAPIBroadcastReceiver_Update extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - int update = intent.getIntExtra(ProviderAPI.CURRENT_PROGRESS, 0); - mProgressBar.setProgress(update); - } - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java deleted file mode 100644 index b388b84a..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ /dev/null @@ -1,479 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package se.leap.bitmaskclient; - -import org.json.JSONException; -import org.json.JSONObject; - -import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.ProviderAPIResultReceiver.Receiver; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.DialogFragment; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.Handler; -import android.os.ResultReceiver; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -/** - * The main user facing Activity of LEAP Android, consisting of status, controls, - * and access to preferences. - * - * @author Sean Leonard - * @author parmegv - */ -public class Dashboard extends Activity implements LogInDialog.LogInDialogInterface,Receiver { - - protected static final int CONFIGURE_LEAP = 0; - protected static final int SWITCH_PROVIDER = 1; - - final public static String SHARED_PREFERENCES = "LEAPPreferences"; - final public static String ACTION_QUIT = "quit"; - public static final String REQUEST_CODE = "request_code"; - - private ProgressBar mProgressBar; - private TextView eipStatus; - private static Context app; - private static SharedPreferences preferences; - private static Provider provider; - - private TextView providerNameTV; - - private boolean authed_eip = false; - - public ProviderAPIResultReceiver providerAPI_result_receiver; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - app = this; - - PRNGFixes.apply(); - // mProgressBar = (ProgressBar) findViewById(R.id.progressbar_dashboard); - // mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); - // eipStatus = (TextView) findViewById(R.id.eipStatus); - - mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); - - preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); - - authed_eip = preferences.getBoolean(EIP.AUTHED_EIP, false); - if (preferences.getString(Provider.KEY, "").isEmpty()) - startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); - else - buildDashboard(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data){ - if ( requestCode == CONFIGURE_LEAP || requestCode == SWITCH_PROVIDER) { - // It should be equivalent: if ( (requestCode == CONFIGURE_LEAP) || (data!= null && data.hasExtra(STOP_FIRST))) { - if ( resultCode == RESULT_OK ){ - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putInt(EIP.PARSED_SERIAL, 0).commit(); - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(EIP.AUTHED_EIP, authed_eip).commit(); - Intent updateEIP = new Intent(getApplicationContext(), EIP.class); - updateEIP.setAction(EIP.ACTION_UPDATE_EIP_SERVICE); - startService(updateEIP); - buildDashboard(); - invalidateOptionsMenu(); - if(data != null && data.hasExtra(LogInDialog.VERB)) { - View view = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0); - logInDialog(view, Bundle.EMPTY); - } - } else if(resultCode == RESULT_CANCELED && data.hasExtra(ACTION_QUIT)) { - finish(); - } else - configErrorDialog(); - } - } - - /** - * Dialog shown when encountering a configuration error. Such errors require - * reconfiguring LEAP or aborting the application. - */ - private void configErrorDialog() { - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getAppContext()); - alertBuilder.setTitle(getResources().getString(R.string.setup_error_title)); - alertBuilder - .setMessage(getResources().getString(R.string.setup_error_text)) - .setCancelable(false) - .setPositiveButton(getResources().getString(R.string.setup_error_configure_button), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - startActivityForResult(new Intent(getAppContext(),ConfigurationWizard.class),CONFIGURE_LEAP); - } - }) - .setNegativeButton(getResources().getString(R.string.setup_error_close_button), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - SharedPreferences.Editor prefsEdit = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE).edit(); - prefsEdit.remove(Provider.KEY).commit(); - finish(); - } - }) - .show(); - } - - /** - * Inflates permanent UI elements of the View and contains logic for what - * service dependent UI elements to include. - */ - private void buildDashboard() { - provider = Provider.getInstance(); - provider.init( this ); - - setContentView(R.layout.client_dashboard); - - providerNameTV = (TextView) findViewById(R.id.providerName); - providerNameTV.setText(provider.getDomain()); - providerNameTV.setTextSize(28); - - mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); - - FragmentManager fragMan = getFragmentManager(); - if ( provider.hasEIP()){ - EipServiceFragment eipFragment = new EipServiceFragment(); - fragMan.beginTransaction().replace(R.id.servicesCollection, eipFragment, EipServiceFragment.TAG).commit(); - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - JSONObject provider_json; - try { - provider_json = new JSONObject(getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.KEY, "")); - JSONObject service_description = provider_json.getJSONObject(Provider.SERVICE); - - if(service_description.getBoolean(Provider.ALLOW_REGISTRATION)) { - if(authed_eip) { - menu.findItem(R.id.login_button).setVisible(false); - menu.findItem(R.id.logout_button).setVisible(true); - } else { - menu.findItem(R.id.login_button).setVisible(true); - menu.findItem(R.id.logout_button).setVisible(false); - } - } - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return true; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.client_dashboard, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item){ - Intent intent; - switch (item.getItemId()){ - case R.id.about_leap: - intent = new Intent(this, AboutActivity.class); - startActivity(intent); - return true; - case R.id.switch_provider: - if (Provider.getInstance().hasEIP()){ - if (getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getBoolean(EIP.AUTHED_EIP, false)){ - logOut(); - } - eipStop(); - } - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().remove(Provider.KEY).commit(); - startActivityForResult(new Intent(this,ConfigurationWizard.class), SWITCH_PROVIDER); - return true; - case R.id.login_button: - View view = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0); - logInDialog(view, Bundle.EMPTY); - return true; - case R.id.logout_button: - logOut(); - return true; - default: - return super.onOptionsItemSelected(item); - } - - } - - @Override - public void authenticate(String username, String password) { - mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); - eipStatus = (TextView) findViewById(R.id.eipStatus); - - providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); - providerAPI_result_receiver.setReceiver(this); - - Intent provider_API_command = new Intent(this, ProviderAPI.class); - - Bundle parameters = new Bundle(); - parameters.putString(LogInDialog.USERNAME, username); - parameters.putString(LogInDialog.PASSWORD, password); - - JSONObject provider_json; - try { - provider_json = new JSONObject(preferences.getString(Provider.KEY, "")); - parameters.putString(Provider.API_URL, provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - provider_API_command.setAction(ProviderAPI.SRP_AUTH); - provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); - provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); - - mProgressBar.setVisibility(ProgressBar.VISIBLE); - eipStatus.setText(R.string.authenticating_message); - //mProgressBar.setMax(4); - startService(provider_API_command); - } - - public void cancelAuthedEipOn() { - EipServiceFragment eipFragment = (EipServiceFragment) getFragmentManager().findFragmentByTag(EipServiceFragment.TAG); - eipFragment.checkEipSwitch(false); - } - - /** - * Asks ProviderAPI to log out. - */ - public void logOut() { - providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); - providerAPI_result_receiver.setReceiver(this); - Intent provider_API_command = new Intent(this, ProviderAPI.class); - - Bundle parameters = new Bundle(); - - JSONObject provider_json; - try { - provider_json = new JSONObject(preferences.getString(Provider.KEY, "")); - parameters.putString(Provider.API_URL, provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - provider_API_command.setAction(ProviderAPI.LOG_OUT); - provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); - provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); - - if(mProgressBar == null) mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); - mProgressBar.setVisibility(ProgressBar.VISIBLE); - if(eipStatus == null) eipStatus = (TextView) findViewById(R.id.eipStatus); - eipStatus.setText(R.string.logout_message); - // eipStatus.setText("Starting to logout"); - - startService(provider_API_command); - //mProgressBar.setMax(1); - - } - - /** - * Shows the log in dialog. - * @param view from which the dialog is created. - */ - public void logInDialog(View view, Bundle resultData) { - FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); - Fragment previous_log_in_dialog = getFragmentManager().findFragmentByTag(LogInDialog.TAG); - if (previous_log_in_dialog != null) { - fragment_transaction.remove(previous_log_in_dialog); - } - fragment_transaction.addToBackStack(null); - - DialogFragment newFragment = LogInDialog.newInstance(); - if(resultData != null && !resultData.isEmpty()) { - newFragment.setArguments(resultData); - } - newFragment.show(fragment_transaction, LogInDialog.TAG); - } - - /** - * Asks ProviderAPI to download an authenticated OpenVPN certificate. - * @param session_id cookie for the server to allow us to download the certificate. - */ - private void downloadAuthedUserCertificate(/*Cookie session_id*/) { - providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); - providerAPI_result_receiver.setReceiver(this); - - Intent provider_API_command = new Intent(this, ProviderAPI.class); - - Bundle parameters = new Bundle(); - parameters.putString(ConfigurationWizard.TYPE_OF_CERTIFICATE, ConfigurationWizard.AUTHED_CERTIFICATE); - /*parameters.putString(ConfigHelper.SESSION_ID_COOKIE_KEY, session_id.getName()); - parameters.putString(ConfigHelper.SESSION_ID_KEY, session_id.getValue());*/ - - provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); - provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); - provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); - - startService(provider_API_command); - } - - @Override - public void onReceiveResult(int resultCode, Bundle resultData) { - if(resultCode == ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL){ - String session_id_cookie_key = resultData.getString(ProviderAPI.SESSION_ID_COOKIE_KEY); - String session_id_string = resultData.getString(ProviderAPI.SESSION_ID_KEY); - setResult(RESULT_OK); - - authed_eip = true; - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(EIP.AUTHED_EIP, authed_eip).commit(); - - invalidateOptionsMenu(); - mProgressBar.setVisibility(ProgressBar.GONE); - changeStatusMessage(resultCode); - - //Cookie session_id = new BasicClientCookie(session_id_cookie_key, session_id_string); - downloadAuthedUserCertificate(/*session_id*/); - } else if(resultCode == ProviderAPI.SRP_AUTHENTICATION_FAILED) { - logInDialog(getCurrentFocus(), resultData); - } else if(resultCode == ProviderAPI.LOGOUT_SUCCESSFUL) { - authed_eip = false; - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(EIP.AUTHED_EIP, authed_eip).commit(); - mProgressBar.setVisibility(ProgressBar.GONE); - mProgressBar.setProgress(0); - invalidateOptionsMenu(); - setResult(RESULT_OK); - changeStatusMessage(resultCode); - - } else if(resultCode == ProviderAPI.LOGOUT_FAILED) { - setResult(RESULT_CANCELED); - changeStatusMessage(resultCode); - mProgressBar.setVisibility(ProgressBar.GONE); - } else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { - setResult(RESULT_OK); - changeStatusMessage(resultCode); - mProgressBar.setVisibility(ProgressBar.GONE); - if(EipServiceFragment.isEipSwitchChecked()) - eipStart(); - } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { - setResult(RESULT_CANCELED); - changeStatusMessage(resultCode); - mProgressBar.setVisibility(ProgressBar.GONE); - } - } - - private void changeStatusMessage(final int previous_result_code) { - // TODO Auto-generated method stub - ResultReceiver eip_status_receiver = new ResultReceiver(new Handler()){ - protected void onReceiveResult(int resultCode, Bundle resultData){ - super.onReceiveResult(resultCode, resultData); - String request = resultData.getString(EIP.REQUEST_TAG); - if (request.equalsIgnoreCase(EIP.ACTION_IS_EIP_RUNNING)){ - if (resultCode == Activity.RESULT_OK){ - - switch(previous_result_code){ - case ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL: eipStatus.setText(R.string.succesful_authentication_message); break; - case ProviderAPI.SRP_AUTHENTICATION_FAILED: eipStatus.setText(R.string.authentication_failed_message); break; - case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.authed_secured_status); break; - case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.incorrectly_downloaded_certificate_message); break; - case ProviderAPI.LOGOUT_SUCCESSFUL: eipStatus.setText(R.string.anonymous_secured_status); break; - case ProviderAPI.LOGOUT_FAILED: eipStatus.setText(R.string.log_out_failed_message); break; - - } - } - else if(resultCode == Activity.RESULT_CANCELED){ - - switch(previous_result_code){ - - case ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL: eipStatus.setText(R.string.succesful_authentication_message); break; - case ProviderAPI.SRP_AUTHENTICATION_FAILED: eipStatus.setText(R.string.authentication_failed_message); break; - case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.future_authed_secured_status); break; - case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.incorrectly_downloaded_certificate_message); break; - case ProviderAPI.LOGOUT_SUCCESSFUL: eipStatus.setText(R.string.future_anonymous_secured_status); break; - case ProviderAPI.LOGOUT_FAILED: eipStatus.setText(R.string.log_out_failed_message); break; - } - } - } - - } - }; - eipIsRunning(eip_status_receiver); - } - - /** - * For retrieving the base application Context in classes that don't extend - * Android's Activity class - * - * @return Application Context as defined by this for Dashboard instance - */ - public static Context getAppContext() { - return app; - } - - - @Override - public void startActivityForResult(Intent intent, int requestCode) { - intent.putExtra(Dashboard.REQUEST_CODE, requestCode); - super.startActivityForResult(intent, requestCode); - } - /** - * Send a command to EIP - * - * @param action A valid String constant from EIP class representing an Intent - * filter for the EIP class - */ - private void eipIsRunning(ResultReceiver eip_receiver){ - // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? - Intent eip_intent = new Intent(this, EIP.class); - eip_intent.setAction(EIP.ACTION_IS_EIP_RUNNING); - eip_intent.putExtra(EIP.RECEIVER_TAG, eip_receiver); - startService(eip_intent); - } - - /** - * Send a command to EIP - * - */ - private void eipStop(){ - // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? - Intent eip_intent = new Intent(this, EIP.class); - eip_intent.setAction(EIP.ACTION_STOP_EIP); - // eip_intent.putExtra(EIP.RECEIVER_TAG, eip_receiver); - startService(eip_intent); - - } - - private void eipStart(){ - Intent eip_intent = new Intent(this, EIP.class); - eip_intent.setAction(EIP.ACTION_START_EIP); - eip_intent.putExtra(EIP.RECEIVER_TAG, EipServiceFragment.getReceiver()); - startService(eip_intent); - - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java b/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java deleted file mode 100644 index f78002b0..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package se.leap.bitmaskclient; - -import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.NewProviderDialog.NewProviderDialogInterface; -import se.leap.bitmaskclient.ProviderListContent.ProviderItem; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.os.Bundle; - -/** - * Implements a dialog to show why a download failed. - * - * @author parmegv - * - */ -public class DownloadFailedDialog extends DialogFragment { - - public static String TAG = "downloaded_failed_dialog"; - private String reason_to_fail; - /** - * @return a new instance of this DialogFragment. - */ - public static DialogFragment newInstance(String reason_to_fail) { - DownloadFailedDialog dialog_fragment = new DownloadFailedDialog(); - dialog_fragment.reason_to_fail = reason_to_fail; - return dialog_fragment; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - builder.setMessage(reason_to_fail) - .setPositiveButton(R.string.retry, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dismiss(); - interface_with_ConfigurationWizard.retrySetUpProvider(); - } - }) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - interface_with_ConfigurationWizard.cancelSettingUpProvider(); - dialog.dismiss(); - } - }); - - // Create the AlertDialog object and return it - return builder.create(); - } - - public interface DownloadFailedDialogInterface { - public void retrySetUpProvider(); - public void cancelSettingUpProvider(); - } - - DownloadFailedDialogInterface interface_with_ConfigurationWizard; - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - interface_with_ConfigurationWizard = (DownloadFailedDialogInterface) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement NoticeDialogListener"); - } - } - - @Override - public void onCancel(DialogInterface dialog) { - interface_with_ConfigurationWizard.cancelSettingUpProvider(); - dialog.dismiss(); - } - -} diff --git a/app/src/main/java/se/leap/bitmaskclient/EIP.java b/app/src/main/java/se/leap/bitmaskclient/EIP.java deleted file mode 100644 index e773e3b9..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/EIP.java +++ /dev/null @@ -1,623 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package se.leap.bitmaskclient; - -import java.util.Calendar; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.TreeMap; -import java.util.Vector; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import se.leap.bitmaskclient.R; -import se.leap.openvpn.ConfigParser; -import se.leap.openvpn.ConfigParser.ConfigParseError; -import se.leap.openvpn.LaunchVPN; -import se.leap.openvpn.OpenVpnManagementThread; -import se.leap.openvpn.OpenVpnService; -import se.leap.openvpn.OpenVpnService.LocalBinder; -import se.leap.openvpn.ProfileManager; -import se.leap.openvpn.VpnProfile; -import android.app.Activity; -import android.app.IntentService; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.drm.DrmStore.Action; -import android.os.Bundle; -import android.os.IBinder; -import android.os.ResultReceiver; -import android.util.Log; - -/** - * EIP is the abstract base class for interacting with and managing the Encrypted - * Internet Proxy connection. Connections are started, stopped, and queried through - * this IntentService. - * Contains logic for parsing eip-service.json from the provider, configuring and selecting - * gateways, and controlling {@link .openvpn.OpenVpnService} connections. - * - * @author Sean Leonard - */ -public final class EIP extends IntentService { - - public final static String AUTHED_EIP = "authed eip"; - public final static String ACTION_START_EIP = "se.leap.bitmaskclient.START_EIP"; - public final static String ACTION_STOP_EIP = "se.leap.bitmaskclient.STOP_EIP"; - public final static String ACTION_UPDATE_EIP_SERVICE = "se.leap.bitmaskclient.UPDATE_EIP_SERVICE"; - public final static String ACTION_IS_EIP_RUNNING = "se.leap.bitmaskclient.IS_RUNNING"; - public final static String EIP_NOTIFICATION = "EIP_NOTIFICATION"; - public final static String ALLOWED_ANON = "allow_anonymous"; - public final static String CERTIFICATE = "cert"; - public final static String PRIVATE_KEY = "private_key"; - public final static String KEY = "eip"; - public final static String PARSED_SERIAL = "eip_parsed_serial"; - public final static String SERVICE_API_PATH = "config/eip-service.json"; - public final static String RECEIVER_TAG = "receiverTag"; - public final static String REQUEST_TAG = "requestTag"; - public final static String TAG = "se.leap.bitmaskclient.EIP"; - - - private static Context context; - private static ResultReceiver mReceiver; - private static OpenVpnService mVpnService; - private static boolean mBound = false; - // Used to store actions to "resume" onServiceConnection - private static String mPending = null; - - private static int parsedEipSerial; - private static JSONObject eipDefinition = null; - - private static OVPNGateway activeGateway = null; - - public EIP(){ - super("LEAPEIP"); - } - - @Override - public void onCreate() { - super.onCreate(); - - context = getApplicationContext(); - - updateEIPService(); - - this.retreiveVpnService(); - } - - @Override - public void onDestroy() { - unbindService(mVpnServiceConn); - mBound = false; - - super.onDestroy(); - } - - @Override - protected void onHandleIntent(Intent intent) { - String action = intent.getAction(); - mReceiver = intent.getParcelableExtra(RECEIVER_TAG); - - if ( action == ACTION_IS_EIP_RUNNING ) - this.isRunning(); - if ( action == ACTION_UPDATE_EIP_SERVICE ) - this.updateEIPService(); - else if ( action == ACTION_START_EIP ) - this.startEIP(); - else if ( action == ACTION_STOP_EIP ) - this.stopEIP(); - } - - /** - * Sends an Intent to bind OpenVpnService. - * Used when OpenVpnService isn't bound but might be running. - */ - private boolean retreiveVpnService() { - Intent bindIntent = new Intent(this,OpenVpnService.class); - bindIntent.setAction(OpenVpnService.RETRIEVE_SERVICE); - return bindService(bindIntent, mVpnServiceConn, BIND_AUTO_CREATE); - } - - private static ServiceConnection mVpnServiceConn = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - LocalBinder binder = (LocalBinder) service; - mVpnService = binder.getService(); - mBound = true; - - if (mReceiver != null && mPending != null) { - - boolean running = mVpnService.isRunning(); - - int resultCode = Activity.RESULT_CANCELED; - - if (mPending.equals(ACTION_IS_EIP_RUNNING)){ - resultCode = (running) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; - - } - else if (mPending.equals(ACTION_START_EIP)){ - resultCode = (running) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; - } - else if (mPending.equals(ACTION_STOP_EIP)){ - resultCode = (running) ? Activity.RESULT_CANCELED - : Activity.RESULT_OK; - } - Bundle resultData = new Bundle(); - resultData.putString(REQUEST_TAG, ACTION_IS_EIP_RUNNING); - mReceiver.send(resultCode, resultData); - - mPending = null; - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mBound = false; - - if (mReceiver != null){ - Bundle resultData = new Bundle(); - resultData.putString(REQUEST_TAG, EIP_NOTIFICATION); - mReceiver.send(Activity.RESULT_CANCELED, resultData); - } - } - - - }; - - /** - * Attempts to determine if OpenVpnService has an established VPN connection - * through the bound ServiceConnection. If there is no bound service, this - * method will attempt to bind a running OpenVpnService and send - * Activity.RESULT_CANCELED to the ResultReceiver that made the - * request. - * Note: If the request to bind OpenVpnService is successful, the ResultReceiver - * will be notified in {@link onServiceConnected()} - */ - - private void isRunning() { - Bundle resultData = new Bundle(); - resultData.putString(REQUEST_TAG, ACTION_IS_EIP_RUNNING); - int resultCode = Activity.RESULT_CANCELED; - if (mBound) { - resultCode = (mVpnService.isRunning()) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; - - if (mReceiver != null){ - mReceiver.send(resultCode, resultData); - } - } else { - mPending = ACTION_IS_EIP_RUNNING; - boolean retrieved_vpn_service = retreiveVpnService(); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - boolean running = false; - try { - running = mVpnService.isRunning(); - } catch (NullPointerException e){ - e.printStackTrace(); - } - - if (retrieved_vpn_service && running && mReceiver != null){ - mReceiver.send(Activity.RESULT_OK, resultData); - } - else{ - mReceiver.send(Activity.RESULT_CANCELED, resultData); - } - } - } - - /** - * Initiates an EIP connection by selecting a gateway and preparing and sending an - * Intent to {@link se.leap.openvpn.LaunchVPN} - */ - private void startEIP() { - activeGateway = selectGateway(); - - Intent intent = new Intent(this,LaunchVPN.class); - intent.setAction(Intent.ACTION_MAIN); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(LaunchVPN.EXTRA_KEY, activeGateway.mVpnProfile.getUUID().toString() ); - intent.putExtra(LaunchVPN.EXTRA_NAME, activeGateway.mVpnProfile.getName() ); - intent.putExtra(RECEIVER_TAG, mReceiver); - startActivity(intent); - mPending = ACTION_START_EIP; - } - - /** - * Disconnects the EIP connection gracefully through the bound service or forcefully - * if there is no bound service. Sends a message to the requesting ResultReceiver. - */ - private void stopEIP() { - if (mBound) - mVpnService.onRevoke(); - else - OpenVpnManagementThread.stopOpenVPN(); - - if (mReceiver != null){ - Bundle resultData = new Bundle(); - resultData.putString(REQUEST_TAG, ACTION_STOP_EIP); - mReceiver.send(Activity.RESULT_OK, resultData); - } - } - - /** - * Loads eip-service.json from SharedPreferences and calls {@link updateGateways()} - * to parse gateway definitions. - * TODO Implement API call to refresh eip-service.json from the provider - */ - private void updateEIPService() { - try { - eipDefinition = new JSONObject(getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(KEY, "")); - parsedEipSerial = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getInt(PARSED_SERIAL, 0); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if(parsedEipSerial == 0) { - // Delete all vpn profiles - ProfileManager vpl = ProfileManager.getInstance(context); - VpnProfile[] profiles = (VpnProfile[]) vpl.getProfiles().toArray(new VpnProfile[vpl.getProfiles().size()]); - for (int current_profile = 0; current_profile < profiles.length; current_profile++){ - vpl.removeProfile(context, profiles[current_profile]); - } - } - if (eipDefinition.optInt("serial") > parsedEipSerial) - updateGateways(); - } - - /** - * Choose a gateway to connect to based on timezone from system locale data - * - * @return The gateway to connect to - */ - private OVPNGateway selectGateway() { - // TODO Remove String arg constructor in favor of findGatewayByName(String) - - Calendar cal = Calendar.getInstance(); - int localOffset = cal.get(Calendar.ZONE_OFFSET) / 3600000; - TreeMap> offsets = new TreeMap>(); - JSONObject locationsObjects = null; - Iterator locations = null; - try { - locationsObjects = eipDefinition.getJSONObject("locations"); - locations = locationsObjects.keys(); - } catch (JSONException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - - while (locations.hasNext()) { - String locationName = locations.next(); - JSONObject location = null; - try { - location = locationsObjects.getJSONObject(locationName); - - // Distance along the numberline of Prime Meridian centric, assumes UTC-11 through UTC+12 - int dist = Math.abs(localOffset - location.optInt("timezone")); - // Farther than 12 timezones and it's shorter around the "back" - if (dist > 12) - dist = 12 - (dist -12); // Well i'll be. Absolute values make equations do funny things. - - Set set = offsets.get(dist); - if (set == null) set = new HashSet(); - set.add(locationName); - offsets.put(dist, set); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - - String closestLocation = offsets.isEmpty() ? "" : offsets.firstEntry().getValue().iterator().next(); - JSONArray gateways = null; - String chosenHost = null; - try { - gateways = eipDefinition.getJSONArray("gateways"); - for (int i = 0; i < gateways.length(); i++) { - JSONObject gw = gateways.getJSONObject(i); - if ( gw.getString("location").equalsIgnoreCase(closestLocation) || closestLocation.isEmpty()){ - chosenHost = gw.getString("host"); - break; - } - } - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - return new OVPNGateway(chosenHost); - } - - /** - * Walk the list of gateways defined in eip-service.json and parse them into - * OVPNGateway objects. - * TODO Store the OVPNGateways (as Serializable) in SharedPreferences - */ - private void updateGateways(){ - JSONArray gatewaysDefined = null; - - try { - gatewaysDefined = eipDefinition.getJSONArray("gateways"); - } catch (JSONException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - - for ( int i=0 ; i < gatewaysDefined.length(); i++ ){ - - JSONObject gw = null; - - try { - gw = gatewaysDefined.getJSONObject(i); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - try { - if ( gw.getJSONObject("capabilities").getJSONArray("transport").toString().contains("openvpn") ){ - new OVPNGateway(gw); - } - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putInt(PARSED_SERIAL, eipDefinition.optInt(Provider.API_RETURN_SERIAL)).commit(); - } - - /** - * OVPNGateway provides objects defining gateways and their options and metadata. - * Each instance contains a VpnProfile for OpenVPN specific data and member - * variables describing capabilities and location - * - * @author Sean Leonard - */ - private class OVPNGateway { - - private String TAG = "OVPNGateway"; - - private String mName; - private VpnProfile mVpnProfile; - private JSONObject mGateway; - private HashMap>> options = new HashMap>>(); - - - /** - * Attempts to retrieve a VpnProfile by name and build an OVPNGateway around it. - * FIXME This needs to become a findGatewayByName() method - * - * @param name The hostname of the gateway to inflate - */ - private OVPNGateway(String name){ - mName = name; - - this.loadVpnProfile(); - } - - private void loadVpnProfile() { - ProfileManager vpl = ProfileManager.getInstance(context); - - try { - if ( mName == null ) - mVpnProfile = vpl.getProfiles().iterator().next(); - else - mVpnProfile = vpl.getProfileByName(mName); - } catch (NoSuchElementException e) { - updateEIPService(); - this.loadVpnProfile(); // FIXME catch infinite loops - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - /** - * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json - * and create a VpnProfile belonging to it. - * - * @param gateway The JSON OpenVPN gateway definition to parse - */ - protected OVPNGateway(JSONObject gateway){ - - mGateway = gateway; - - // Currently deletes VpnProfile for host, if there already is one, and builds new - ProfileManager vpl = ProfileManager.getInstance(context); - Collection profiles = vpl.getProfiles(); - for (Iterator it = profiles.iterator(); it.hasNext(); ){ - VpnProfile p = it.next(); - try { - if ( p.mName.equalsIgnoreCase( gateway.getString("host") ) ){ - it.remove(); - vpl.removeProfile(context, p); - } - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - this.parseOptions(); - this.createVPNProfile(); - - setUniqueProfileName(vpl); - vpl.addProfile(mVpnProfile); - vpl.saveProfile(context, mVpnProfile); - vpl.saveProfileList(context); - } - - /** - * Attempts to create a unique profile name from the hostname of the gateway - * - * @param profileManager - */ - private void setUniqueProfileName(ProfileManager profileManager) { - int i=0; - - String newname; - try { - newname = mGateway.getString("host"); - while(profileManager.getProfileByName(newname)!=null) { - i++; - if(i==1) - newname = getString(R.string.converted_profile); - else - newname = getString(R.string.converted_profile_i,i); - } - - mVpnProfile.mName=newname; - } catch (JSONException e) { - // TODO Auto-generated catch block - Log.v(TAG,"Couldn't read gateway name for profile creation!"); - e.printStackTrace(); - } - } - - /** - * FIXME This method is really the outline of the refactoring needed in se.leap.openvpn.ConfigParser - */ - private void parseOptions(){ - - // FIXME move these to a common API (& version) definition place, like ProviderAPI or ConfigHelper - String common_options = "openvpn_configuration"; - String remote = "ip_address"; - String ports = "ports"; - String protos = "protocols"; - String capabilities = "capabilities"; - String location_key = "location"; - String locations = "locations"; - - Vector arg = new Vector(); - Vector> args = new Vector>(); - - try { - JSONObject def = (JSONObject) eipDefinition.get(common_options); - Iterator keys = def.keys(); - Vector> value = new Vector>(); - while ( keys.hasNext() ){ - String key = keys.next().toString(); - - arg.add(key); - for ( String word : def.getString(key).split(" ") ) - arg.add(word); - value.add( (Vector) arg.clone() ); - options.put(key, (Vector>) value.clone()); - value.clear(); - arg.clear(); - } - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - try { - arg.add(remote); - arg.add(mGateway.getString(remote)); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - args.add((Vector) arg.clone()); - options.put("remote", (Vector>) args.clone() ); - arg.clear(); - args.clear(); - - - - try { - - arg.add(location_key); - String locationText = ""; - locationText = eipDefinition.getJSONObject(locations).getJSONObject(mGateway.getString(location_key)).getString("name"); - arg.add(locationText); - - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - args.add((Vector) arg.clone()); - options.put("location", (Vector>) args.clone() ); - - arg.clear(); - args.clear(); - JSONArray protocolsJSON = null; - arg.add("proto"); - try { - protocolsJSON = mGateway.getJSONObject(capabilities).getJSONArray(protos); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - Vector protocols = new Vector(); - for ( int i=0; i) arg.clone()); - options.put("proto", (Vector>) args.clone()); - arg.clear(); - args.clear(); - - - String port = null; - arg.add("port"); - try { - port = mGateway.getJSONObject(capabilities).getJSONArray(ports).optString(0); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - arg.add(port); - args.add((Vector) arg.clone()); - options.put("port", (Vector>) args.clone()); - args.clear(); - arg.clear(); - } - - /** - * Create and attach the VpnProfile to our gateway object - */ - protected void createVPNProfile(){ - try { - ConfigParser cp = new ConfigParser(); - cp.setDefinition(options); - VpnProfile vp = cp.convertProfile(); - mVpnProfile = vp; - Log.v(TAG,"Created VPNProfile"); - } catch (ConfigParseError e) { - // FIXME We didn't get a VpnProfile! Error handling! and log level - Log.v(TAG,"Error createing VPNProfile"); - e.printStackTrace(); - } - } - } - -} diff --git a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java deleted file mode 100644 index b4cb541a..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java +++ /dev/null @@ -1,306 +0,0 @@ -package se.leap.bitmaskclient; - -import se.leap.bitmaskclient.R; -import se.leap.openvpn.LogWindow; -import se.leap.openvpn.OpenVPN; -import se.leap.openvpn.OpenVPN.StateListener; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Fragment; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.ResultReceiver; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.CompoundButton; -import android.widget.RelativeLayout; -import android.widget.Switch; -import android.widget.TextView; - -public class EipServiceFragment extends Fragment implements StateListener, OnCheckedChangeListener { - - protected static final String IS_EIP_PENDING = "is_eip_pending"; - - private View eipFragment; - private static Switch eipSwitch; - private View eipDetail; - private TextView eipStatus; - - private boolean eipAutoSwitched = true; - - private boolean mEipStartPending = false; - - private boolean set_switch_off = false; - - private static EIPReceiver mEIPReceiver; - - - public static String TAG = "se.leap.bitmask.EipServiceFragment"; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - eipFragment = inflater.inflate(R.layout.eip_service_fragment, container, false); - - eipDetail = ((RelativeLayout) eipFragment.findViewById(R.id.eipDetail)); - eipDetail.setVisibility(View.VISIBLE); - - View eipSettings = eipFragment.findViewById(R.id.eipSettings); - eipSettings.setVisibility(View.GONE); // FIXME too! - - if (mEipStartPending) - eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); - - eipStatus = (TextView) eipFragment.findViewById(R.id.eipStatus); - - eipSwitch = (Switch) eipFragment.findViewById(R.id.eipSwitch); - - - eipSwitch.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - eipAutoSwitched = false; - return false; - } - }); - eipSwitch.setOnCheckedChangeListener(this); - - - return eipFragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mEIPReceiver = new EIPReceiver(new Handler()); - - if (savedInstanceState != null) - mEipStartPending = savedInstanceState.getBoolean(IS_EIP_PENDING); - } - - @Override - public void onResume() { - super.onResume(); - - OpenVPN.addStateListener(this); - if(set_switch_off) { - eipSwitch.setChecked(false); - set_switch_off = false; - } - } - - protected void setSwitchOff(boolean value) { - set_switch_off = value; - } - - @Override - public void onPause() { - super.onPause(); - - OpenVPN.removeStateListener(this); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(IS_EIP_PENDING, mEipStartPending); - } - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (buttonView.equals(eipSwitch) && !eipAutoSwitched){ - boolean allowed_anon = getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).getBoolean(EIP.ALLOWED_ANON, false); - String certificate = getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).getString(EIP.CERTIFICATE, ""); - if(allowed_anon || !certificate.isEmpty()) { - if (isChecked){ - mEipStartPending = true; - eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); - ((TextView) eipFragment.findViewById(R.id.eipStatus)).setText(R.string.eip_status_start_pending); - eipCommand(EIP.ACTION_START_EIP); - } else { - if (mEipStartPending){ - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity()); - alertBuilder.setTitle(getResources().getString(R.string.eip_cancel_connect_title)); - alertBuilder - .setMessage(getResources().getString(R.string.eip_cancel_connect_text)) - .setPositiveButton(getResources().getString(R.string.eip_cancel_connect_cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - eipCommand(EIP.ACTION_STOP_EIP); - mEipStartPending = false; - } - }) - .setNegativeButton(getResources().getString(R.string.eip_cancel_connect_false), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - eipAutoSwitched = true; - eipSwitch.setChecked(true); - eipAutoSwitched = false; - } - }) - .show(); - } else { - eipCommand(EIP.ACTION_STOP_EIP); - } - } - } - else { - Dashboard dashboard = (Dashboard)getActivity(); - Bundle waiting_on_login = new Bundle(); - waiting_on_login.putBoolean(IS_EIP_PENDING, true); - dashboard.logInDialog(getActivity().getCurrentFocus(), waiting_on_login); - } - } - else { - if(!eipSwitch.isChecked()) - eipStatus.setText(R.string.state_noprocess); - } - eipAutoSwitched = true; - } - - - - /** - * Send a command to EIP - * - * @param action A valid String constant from EIP class representing an Intent - * filter for the EIP class - */ - private void eipCommand(String action){ - // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? - Intent vpnIntent = new Intent(action); - vpnIntent.putExtra(EIP.RECEIVER_TAG, mEIPReceiver); - getActivity().startService(vpnIntent); - } - - @Override - public void updateState(final String state, final String logmessage, final int localizedResId) { - // Note: "states" are not organized anywhere...collected state strings: - // NOPROCESS,NONETWORK,BYTECOUNT,AUTH_FAILED + some parsing thing ( WAIT(?),AUTH,GET_CONFIG,ASSIGN_IP,CONNECTED,SIGINT ) - getActivity().runOnUiThread(new Runnable() { - - @Override - public void run() { - if (eipStatus != null) { - boolean switchState = true; - String statusMessage = ""; - String prefix = getString(localizedResId); - if (state.equals("CONNECTED")){ - - statusMessage = getString(R.string.eip_state_connected); - getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE); - mEipStartPending = false; - } else if (state.equals("BYTECOUNT")) { - statusMessage = getString(R.string.eip_state_connected); getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE); - mEipStartPending = false; - - } else if ( (state.equals("NOPROCESS") && !mEipStartPending ) || state.equals("EXITING") && !mEipStartPending || state.equals("FATAL")) { - statusMessage = getString(R.string.eip_state_not_connected); - getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE); - mEipStartPending = false; - switchState = false; - } else if (state.equals("NOPROCESS")){ - statusMessage = logmessage; - } else if (state.equals("ASSIGN_IP")){ //don't show assigning message in eipStatus - statusMessage = (String) eipStatus.getText(); - } - else { - statusMessage = prefix + " " + logmessage; - } - - eipAutoSwitched = true; - eipSwitch.setChecked(switchState); - eipAutoSwitched = false; - eipStatus.setText(statusMessage); - } - } - }); - } - - - /** - * Inner class for handling messages related to EIP status and control requests - * - * @author Sean Leonard - */ - protected class EIPReceiver extends ResultReceiver { - - protected EIPReceiver(Handler handler){ - super(handler); - } - - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - super.onReceiveResult(resultCode, resultData); - - String request = resultData.getString(EIP.REQUEST_TAG); - boolean checked = false; - - if (request == EIP.ACTION_IS_EIP_RUNNING) { - switch (resultCode){ - case Activity.RESULT_OK: - checked = true; - break; - case Activity.RESULT_CANCELED: - checked = false; - break; - } - } else if (request == EIP.ACTION_START_EIP) { - switch (resultCode){ - case Activity.RESULT_OK: - checked = true; - break; - case Activity.RESULT_CANCELED: - checked = false; - eipFragment.findViewById(R.id.eipProgress).setVisibility(View.GONE); - break; - } - } else if (request == EIP.ACTION_STOP_EIP) { - switch (resultCode){ - case Activity.RESULT_OK: - checked = false; - break; - case Activity.RESULT_CANCELED: - checked = true; - break; - } - } else if (request == EIP.EIP_NOTIFICATION) { - switch (resultCode){ - case Activity.RESULT_OK: - checked = true; - break; - case Activity.RESULT_CANCELED: - checked = false; - break; - } - } - - eipAutoSwitched = true; - eipSwitch.setChecked(checked); - eipAutoSwitched = false; - } - } - - - public static EIPReceiver getReceiver() { - return mEIPReceiver; - } - - public static boolean isEipSwitchChecked() { - return eipSwitch.isChecked(); - } - - public void checkEipSwitch(boolean checked) { - eipSwitch.setChecked(checked); - onCheckedChanged(eipSwitch, checked); - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/LeapHttpClient.java b/app/src/main/java/se/leap/bitmaskclient/LeapHttpClient.java deleted file mode 100644 index 885b5105..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/LeapHttpClient.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package se.leap.bitmaskclient; - -import java.security.KeyStore; - -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.SingleClientConnManager; -import android.content.Context; - -/** - * Implements an HTTP client, enabling LEAP Android app to manage its own runtime keystore or bypass default Android security measures. - * - * @author rafa - * - */ -public class LeapHttpClient extends DefaultHttpClient { - - private static LeapHttpClient client; - - /** - * If the class scope client is null, it creates one and imports, if existing, the main certificate from Shared Preferences. - * @param context - * @return the new client. - */ - public static LeapHttpClient getInstance(String cert_string) { - if(client == null) { - if(cert_string != null) { - ConfigHelper.addTrustedCertificate("provider_ca_certificate", cert_string); - } - } - return client; - } - - @Override - protected ClientConnectionManager createClientConnectionManager() { - SchemeRegistry registry = new SchemeRegistry(); - registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); - registry.register(new Scheme("https", newSslSocketFactory(), 443)); - - return new SingleClientConnManager(getParams(), registry); - } - - /** - * Uses keystore from ConfigHelper for the SSLSocketFactory. - * @return - */ - private SSLSocketFactory newSslSocketFactory() { - try { - KeyStore trusted = ConfigHelper.getKeystore(); - SSLSocketFactory sf = new SSLSocketFactory(trusted); - - return sf; - } catch (Exception e) { - throw new AssertionError(e); - } - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java deleted file mode 100644 index a317d95e..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java +++ /dev/null @@ -1,341 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package se.leap.bitmaskclient; - -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Arrays; - -import org.jboss.security.srp.SRPParameters; - -/** - * Implements all SRP algorithm logic. - * - * It's derived from JBoss implementation, with adjustments to make it work with LEAP platform. - * - * @author parmegv - * - */ -public class LeapSRPSession { - - private static String token = ""; - - final public static String SALT = "salt"; - final public static String M1 = "M1"; - final public static String M2 = "M2"; - final public static String TOKEN = "token"; - final public static String AUTHORIZATION_HEADER= "Authorization"; - - private SRPParameters params; - private String username; - private String password; - private BigInteger N; - private byte[] N_bytes; - private BigInteger g; - private BigInteger x; - private BigInteger v; - private BigInteger a; - private BigInteger A; - private byte[] K; - private SecureRandom pseudoRng; - /** The M1 = H(H(N) xor H(g) | H(U) | s | A | B | K) hash */ - private MessageDigest clientHash; - /** The M2 = H(A | M | K) hash */ - private MessageDigest serverHash; - - private static int A_LEN; - - /** Creates a new SRP server session object from the username, password - verifier, - @param username, the user ID - @param password, the user clear text password - @param params, the SRP parameters for the session - */ - public LeapSRPSession(String username, String password, SRPParameters params) - { - this(username, password, params, null); - } - - /** Creates a new SRP server session object from the username, password - verifier, - @param username, the user ID - @param password, the user clear text password - @param params, the SRP parameters for the session - @param abytes, the random exponent used in the A public key - */ - public LeapSRPSession(String username, String password, SRPParameters params, - byte[] abytes) { - this.params = params; - this.g = new BigInteger(1, params.g); - N_bytes = ConfigHelper.trim(params.N); - this.N = new BigInteger(1, N_bytes); - this.username = username; - this.password = password; - - try { - pseudoRng = SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - if( abytes != null ) { - A_LEN = 8*abytes.length; - /* TODO Why did they put this condition? - if( 8*abytes.length != A_LEN ) - throw new IllegalArgumentException("The abytes param must be " - +(A_LEN/8)+" in length, abytes.length="+abytes.length); - */ - this.a = new BigInteger(abytes); - } - else - A_LEN = 64; - - serverHash = newDigest(); - clientHash = newDigest(); - } - - /** - * Calculates the parameter x of the SRP-6a algorithm. - * @param username - * @param password - * @param salt the salt of the user - * @return x - */ - public byte[] calculatePasswordHash(String username, String password, byte[] salt) - { - //password = password.replaceAll("\\\\", "\\\\\\\\"); - // Calculate x = H(s | H(U | ':' | password)) - MessageDigest x_digest = newDigest(); - // Try to convert the username to a byte[] using ISO-8859-1 - byte[] user = null; - byte[] password_bytes = null; - byte[] colon = {}; - String encoding = "ISO-8859-1"; - try { - user = ConfigHelper.trim(username.getBytes(encoding)); - colon = ConfigHelper.trim(":".getBytes(encoding)); - password_bytes = ConfigHelper.trim(password.getBytes(encoding)); - } - catch(UnsupportedEncodingException e) { - // Use the default platform encoding - user = ConfigHelper.trim(username.getBytes()); - colon = ConfigHelper.trim(":".getBytes()); - password_bytes = ConfigHelper.trim(password.getBytes()); - } - - // Build the hash - x_digest.update(user); - x_digest.update(colon); - x_digest.update(password_bytes); - byte[] h = x_digest.digest(); - - x_digest.reset(); - x_digest.update(salt); - x_digest.update(h); - byte[] x_digest_bytes = x_digest.digest(); - - return x_digest_bytes; - } - - /** - * Calculates the parameter V of the SRP-6a algorithm. - * @param k_string constant k predefined by the SRP server implementation. - * @return the value of V - */ - private BigInteger calculateV(String k_string) { - BigInteger k = new BigInteger(k_string, 16); - BigInteger v = k.multiply(g.modPow(x, N)); // g^x % N - return v; - } - - /** - * Calculates the trimmed xor from two BigInteger numbers - * @param b1 the positive source to build first BigInteger - * @param b2 the positive source to build second BigInteger - * @param length - * @return - */ - public byte[] xor(byte[] b1, byte[] b2) - { - //TODO Check if length matters in the order, when b2 is smaller than b1 or viceversa - byte[] xor_digest = new BigInteger(1, b1).xor(new BigInteger(1, b2)).toByteArray(); - return ConfigHelper.trim(xor_digest); - } - - /** - * @returns The exponential residue (parameter A) to be sent to the server. - */ - public byte[] exponential() { - byte[] Abytes = null; - if(A == null) { - /* If the random component of A has not been specified use a random - number */ - if( a == null ) { - BigInteger one = BigInteger.ONE; - do { - a = new BigInteger(A_LEN, pseudoRng); - } while(a.compareTo(one) <= 0); - } - A = g.modPow(a, N); - Abytes = ConfigHelper.trim(A.toByteArray()); - } - return Abytes; - } - - /** - * Calculates the parameter M1, to be sent to the SRP server. - * It also updates hashes of client and server for further calculations in other methods. - * It uses a predefined k. - * @param salt_bytes - * @param Bbytes the parameter received from the server, in bytes - * @return the parameter M1 - * @throws NoSuchAlgorithmException - */ - public byte[] response(byte[] salt_bytes, byte[] Bbytes) throws NoSuchAlgorithmException { - // Calculate x = H(s | H(U | ':' | password)) - byte[] M1 = null; - if(new BigInteger(1, Bbytes).mod(new BigInteger(1, N_bytes)) != BigInteger.ZERO) { - byte[] xb = calculatePasswordHash(username, password, ConfigHelper.trim(salt_bytes)); - this.x = new BigInteger(1, xb); - - // Calculate v = kg^x mod N - String k_string = "bf66c44a428916cad64aa7c679f3fd897ad4c375e9bbb4cbf2f5de241d618ef0"; - this.v = calculateV(k_string); - - // H(N) - byte[] digest_of_n = newDigest().digest(N_bytes); - - // H(g) - byte[] digest_of_g = newDigest().digest(params.g); - - // clientHash = H(N) xor H(g) - byte[] xor_digest = xor(digest_of_n, digest_of_g); - clientHash.update(xor_digest); - - // clientHash = H(N) xor H(g) | H(U) - byte[] username_digest = newDigest().digest(ConfigHelper.trim(username.getBytes())); - username_digest = ConfigHelper.trim(username_digest); - clientHash.update(username_digest); - - // clientHash = H(N) xor H(g) | H(U) | s - clientHash.update(ConfigHelper.trim(salt_bytes)); - - K = null; - - // clientHash = H(N) xor H(g) | H(U) | A - byte[] Abytes = ConfigHelper.trim(A.toByteArray()); - clientHash.update(Abytes); - - // clientHash = H(N) xor H(g) | H(U) | s | A | B - Bbytes = ConfigHelper.trim(Bbytes); - clientHash.update(Bbytes); - - // Calculate S = (B - kg^x) ^ (a + u * x) % N - BigInteger S = calculateS(Bbytes); - byte[] S_bytes = ConfigHelper.trim(S.toByteArray()); - - // K = SessionHash(S) - String hash_algorithm = params.hashAlgorithm; - MessageDigest sessionDigest = MessageDigest.getInstance(hash_algorithm); - K = ConfigHelper.trim(sessionDigest.digest(S_bytes)); - - // clientHash = H(N) xor H(g) | H(U) | A | B | K - clientHash.update(K); - - M1 = ConfigHelper.trim(clientHash.digest()); - - // serverHash = Astr + M + K - serverHash.update(Abytes); - serverHash.update(M1); - serverHash.update(K); - - } - return M1; - } - - /** - * It calculates the parameter S used by response() to obtain session hash K. - * @param Bbytes the parameter received from the server, in bytes - * @return the parameter S - */ - private BigInteger calculateS(byte[] Bbytes) { - byte[] Abytes = ConfigHelper.trim(A.toByteArray()); - Bbytes = ConfigHelper.trim(Bbytes); - byte[] u_bytes = getU(Abytes, Bbytes); - - BigInteger B = new BigInteger(1, Bbytes); - BigInteger u = new BigInteger(1, u_bytes); - - BigInteger B_minus_v = B.subtract(v); - BigInteger a_ux = a.add(u.multiply(x)); - BigInteger S = B_minus_v.modPow(a_ux, N); - return S; - } - - /** - * It calculates the parameter u used by calculateS to obtain S. - * @param Abytes the exponential residue sent to the server - * @param Bbytes the parameter received from the server, in bytes - * @return - */ - public byte[] getU(byte[] Abytes, byte[] Bbytes) { - MessageDigest u_digest = newDigest(); - u_digest.update(ConfigHelper.trim(Abytes)); - u_digest.update(ConfigHelper.trim(Bbytes)); - byte[] u_digest_bytes = u_digest.digest(); - return ConfigHelper.trim(new BigInteger(1, u_digest_bytes).toByteArray()); - } - - /** - * @param M2 The server's response to the client's challenge - * @returns True if and only if the server's response was correct. - */ - public boolean verify(byte[] M2) - { - // M2 = H(A | M1 | K) - M2 = ConfigHelper.trim(M2); - byte[] myM2 = ConfigHelper.trim(serverHash.digest()); - boolean valid = Arrays.equals(M2, myM2); - return valid; - } - - protected static void setToken(String token) { - LeapSRPSession.token = token; - } - - protected static String getToken() { - return token; - } - - /** - * @return a new SHA-256 digest. - */ - public MessageDigest newDigest() - { - MessageDigest md = null; - try { - md = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - return md; - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/LogInDialog.java b/app/src/main/java/se/leap/bitmaskclient/LogInDialog.java deleted file mode 100644 index a28c9049..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/LogInDialog.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package se.leap.bitmaskclient; - -import se.leap.bitmaskclient.R; -import android.R.color; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.content.res.ColorStateList; -import android.os.Bundle; -import android.provider.CalendarContract.Colors; -import android.view.LayoutInflater; -import android.view.View; -import android.view.animation.AlphaAnimation; -import android.view.animation.BounceInterpolator; -import android.widget.EditText; -import android.widget.TextView; - -/** - * Implements the log in dialog, currently without progress dialog. - * - * It returns to the previous fragment when finished, and sends username and password to the authenticate method. - * - * It also notifies the user if the password is not valid. - * - * @author parmegv - * - */ -public class LogInDialog extends DialogFragment { - - - final public static String TAG = "logInDialog"; - final public static String VERB = "log in"; - final public static String USERNAME = "username"; - final public static String PASSWORD = "password"; - final public static String USERNAME_MISSING = "username missing"; - final public static String PASSWORD_INVALID_LENGTH = "password_invalid_length"; - - private static boolean is_eip_pending = false; - - public AlertDialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - LayoutInflater inflater = getActivity().getLayoutInflater(); - View log_in_dialog_view = inflater.inflate(R.layout.log_in_dialog, null); - - final TextView user_message = (TextView)log_in_dialog_view.findViewById(R.id.user_message); - if(getArguments() != null && getArguments().containsKey(getResources().getString(R.string.user_message))) { - user_message.setText(getArguments().getString(getResources().getString(R.string.user_message))); - } else { - user_message.setVisibility(View.GONE); - } - - final EditText username_field = (EditText)log_in_dialog_view.findViewById(R.id.username_entered); - if(getArguments() != null && getArguments().containsKey(USERNAME)) { - String username = getArguments().getString(USERNAME); - username_field.setText(username); - } - if (getArguments() != null && getArguments().containsKey(USERNAME_MISSING)) { - username_field.setError(getResources().getString(R.string.username_ask)); - } - - final EditText password_field = (EditText)log_in_dialog_view.findViewById(R.id.password_entered); - if(!username_field.getText().toString().isEmpty() && password_field.isFocusable()) { - password_field.requestFocus(); - } - if (getArguments() != null && getArguments().containsKey(PASSWORD_INVALID_LENGTH)) { - password_field.setError(getResources().getString(R.string.error_not_valid_password_user_message)); - } - if(getArguments() != null && getArguments().getBoolean(EipServiceFragment.IS_EIP_PENDING, false)) { - is_eip_pending = true; - } - - - builder.setView(log_in_dialog_view) - .setPositiveButton(R.string.login_button, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - String username = username_field.getText().toString(); - String password = password_field.getText().toString(); - dialog.dismiss(); - interface_with_Dashboard.authenticate(username, password); - } - }) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - - return builder.create(); - } - - /** - * Interface used to communicate LogInDialog with Dashboard. - * - * @author parmegv - * - */ - public interface LogInDialogInterface { - /** - * Starts authentication process. - * @param username - * @param password - */ - public void authenticate(String username, String password); - public void cancelAuthedEipOn(); - } - - LogInDialogInterface interface_with_Dashboard; - - /** - * @return a new instance of this DialogFragment. - */ - public static DialogFragment newInstance() { - LogInDialog dialog_fragment = new LogInDialog(); - return dialog_fragment; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - interface_with_Dashboard = (LogInDialogInterface) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement LogInDialogListener"); - } - } - - @Override - public void onCancel(DialogInterface dialog) { - if(is_eip_pending) - interface_with_Dashboard.cancelAuthedEipOn(); - super.onCancel(dialog); - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/NewProviderDialog.java b/app/src/main/java/se/leap/bitmaskclient/NewProviderDialog.java deleted file mode 100644 index cf09c64b..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/NewProviderDialog.java +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package se.leap.bitmaskclient; - -import se.leap.bitmaskclient.ProviderListContent.ProviderItem; -import se.leap.bitmaskclient.R; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.Toast; - -/** - * Implements the new custom provider dialog. - * - * @author parmegv - * - */ -public class NewProviderDialog extends DialogFragment { - - final public static String TAG = "newProviderDialog"; - - public interface NewProviderDialogInterface { - public void showAndSelectProvider(String url_provider, boolean danger_on); - } - - NewProviderDialogInterface interface_with_ConfigurationWizard; - - /** - * @return a new instance of this DialogFragment. - */ - public static DialogFragment newInstance() { - NewProviderDialog dialog_fragment = new NewProviderDialog(); - return dialog_fragment; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - interface_with_ConfigurationWizard = (NewProviderDialogInterface) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement NoticeDialogListener"); - } - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - LayoutInflater inflater = getActivity().getLayoutInflater(); - View new_provider_dialog_view = inflater.inflate(R.layout.new_provider_dialog, null); - final EditText url_input_field = (EditText)new_provider_dialog_view.findViewById(R.id.new_provider_url); - if(getArguments() != null && getArguments().containsKey(Provider.MAIN_URL)) { - url_input_field.setText(getArguments().getString(Provider.MAIN_URL)); - } - final CheckBox danger_checkbox = (CheckBox)new_provider_dialog_view.findViewById(R.id.danger_checkbox); - if(getArguments() != null && getArguments().containsKey(ProviderItem.DANGER_ON)) { - danger_checkbox.setActivated(getArguments().getBoolean(ProviderItem.DANGER_ON)); - } - - builder.setView(new_provider_dialog_view) - .setMessage(R.string.introduce_new_provider) - .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - String entered_url = url_input_field.getText().toString().trim(); - if(!entered_url.startsWith("https://")) { - if (entered_url.startsWith("http://")){ - entered_url = entered_url.substring("http://".length()); - } - entered_url = "https://".concat(entered_url); - } - boolean danger_on = danger_checkbox.isChecked(); - if(validURL(entered_url)) { - interface_with_ConfigurationWizard.showAndSelectProvider(entered_url, danger_on); - Toast.makeText(getActivity().getApplicationContext(), R.string.valid_url_entered, Toast.LENGTH_LONG).show(); - } else { - url_input_field.setText(""); - danger_checkbox.setChecked(false); - Toast.makeText(getActivity().getApplicationContext(), R.string.not_valid_url_entered, Toast.LENGTH_LONG).show();; - } - } - }) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - // Create the AlertDialog object and return it - return builder.create(); - } - - /** - * Checks if the entered url is valid or not. - * @param entered_url - * @return true if it's not empty nor contains only the protocol. - */ - boolean validURL(String entered_url) { - //return !entered_url.isEmpty() && entered_url.matches("http[s]?://.+") && !entered_url.replaceFirst("http[s]?://", "").isEmpty(); - return android.util.Patterns.WEB_URL.matcher(entered_url).matches(); - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/PRNGFixes.java b/app/src/main/java/se/leap/bitmaskclient/PRNGFixes.java deleted file mode 100644 index a046f01f..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/PRNGFixes.java +++ /dev/null @@ -1,338 +0,0 @@ -package se.leap.bitmaskclient; - -/* - * This software is provided 'as-is', without any express or implied - * warranty. In no event will Google be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, as long as the origin is not misrepresented. - * - * Source: http://android-developers.blogspot.de/2013/08/some-securerandom-thoughts.html - */ - -import android.os.Build; -import android.os.Process; -import android.util.Log; - -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.SecureRandomSpi; -import java.security.Security; - -/** - * Fixes for the output of the default PRNG having low entropy. - * - * The fixes need to be applied via {@link #apply()} before any use of Java - * Cryptography Architecture primitives. A good place to invoke them is in the - * application's {@code onCreate}. - */ -public final class PRNGFixes { - - private static final int VERSION_CODE_JELLY_BEAN = 16; - private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; - private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = - getBuildFingerprintAndDeviceSerial(); - - /** Hidden constructor to prevent instantiation. */ - private PRNGFixes() {} - - /** - * Applies all fixes. - * - * @throws SecurityException if a fix is needed but could not be applied. - */ - public static void apply() { - applyOpenSSLFix(); - installLinuxPRNGSecureRandom(); - } - - /** - * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the - * fix is not needed. - * - * @throws SecurityException if the fix is needed but could not be applied. - */ - private static void applyOpenSSLFix() throws SecurityException { - if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) - || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { - // No need to apply the fix - return; - } - - try { - // Mix in the device- and invocation-specific seed. - Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_seed", byte[].class) - .invoke(null, generateSeed()); - - // Mix output of Linux PRNG into OpenSSL's PRNG - int bytesRead = (Integer) Class.forName( - "org.apache.harmony.xnet.provider.jsse.NativeCrypto") - .getMethod("RAND_load_file", String.class, long.class) - .invoke(null, "/dev/urandom", 1024); - if (bytesRead != 1024) { - throw new IOException( - "Unexpected number of bytes read from Linux PRNG: " - + bytesRead); - } - } catch (Exception e) { - throw new SecurityException("Failed to seed OpenSSL PRNG", e); - } - } - - /** - * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the - * default. Does nothing if the implementation is already the default or if - * there is not need to install the implementation. - * - * @throws SecurityException if the fix is needed but could not be applied. - */ - private static void installLinuxPRNGSecureRandom() - throws SecurityException { - if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { - // No need to apply the fix - return; - } - - // Install a Linux PRNG-based SecureRandom implementation as the - // default, if not yet installed. - Provider[] secureRandomProviders = - Security.getProviders("SecureRandom.SHA1PRNG"); - if ((secureRandomProviders == null) - || (secureRandomProviders.length < 1) - || (!LinuxPRNGSecureRandomProvider.class.equals( - secureRandomProviders[0].getClass()))) { - Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); - } - - // Assert that new SecureRandom() and - // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed - // by the Linux PRNG-based SecureRandom implementation. - SecureRandom rng1 = new SecureRandom(); - if (!LinuxPRNGSecureRandomProvider.class.equals( - rng1.getProvider().getClass())) { - throw new SecurityException( - "new SecureRandom() backed by wrong Provider: " - + rng1.getProvider().getClass()); - } - - SecureRandom rng2; - try { - rng2 = SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException("SHA1PRNG not available", e); - } - if (!LinuxPRNGSecureRandomProvider.class.equals( - rng2.getProvider().getClass())) { - throw new SecurityException( - "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" - + " Provider: " + rng2.getProvider().getClass()); - } - } - - /** - * {@code Provider} of {@code SecureRandom} engines which pass through - * all requests to the Linux PRNG. - */ - private static class LinuxPRNGSecureRandomProvider extends Provider { - - public LinuxPRNGSecureRandomProvider() { - super("LinuxPRNG", - 1.0, - "A Linux-specific random number provider that uses" - + " /dev/urandom"); - // Although /dev/urandom is not a SHA-1 PRNG, some apps - // explicitly request a SHA1PRNG SecureRandom and we thus need to - // prevent them from getting the default implementation whose output - // may have low entropy. - put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); - put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); - } - } - - /** - * {@link SecureRandomSpi} which passes all requests to the Linux PRNG - * ({@code /dev/urandom}). - */ - public static class LinuxPRNGSecureRandom extends SecureRandomSpi { - - /* - * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed - * are passed through to the Linux PRNG (/dev/urandom). Instances of - * this class seed themselves by mixing in the current time, PID, UID, - * build fingerprint, and hardware serial number (where available) into - * Linux PRNG. - * - * Concurrency: Read requests to the underlying Linux PRNG are - * serialized (on sLock) to ensure that multiple threads do not get - * duplicated PRNG output. - */ - - private static final File URANDOM_FILE = new File("/dev/urandom"); - - private static final Object sLock = new Object(); - - /** - * Input stream for reading from Linux PRNG or {@code null} if not yet - * opened. - * - * @GuardedBy("sLock") - */ - private static DataInputStream sUrandomIn; - - /** - * Output stream for writing to Linux PRNG or {@code null} if not yet - * opened. - * - * @GuardedBy("sLock") - */ - private static OutputStream sUrandomOut; - - /** - * Whether this engine instance has been seeded. This is needed because - * each instance needs to seed itself if the client does not explicitly - * seed it. - */ - private boolean mSeeded; - - @Override - protected void engineSetSeed(byte[] bytes) { - try { - OutputStream out; - synchronized (sLock) { - out = getUrandomOutputStream(); - } - out.write(bytes); - out.flush(); - } catch (IOException e) { - // On a small fraction of devices /dev/urandom is not writable. - // Log and ignore. - Log.w(PRNGFixes.class.getSimpleName(), - "Failed to mix seed into " + URANDOM_FILE); - } finally { - mSeeded = true; - } - } - - @Override - protected void engineNextBytes(byte[] bytes) { - if (!mSeeded) { - // Mix in the device- and invocation-specific seed. - engineSetSeed(generateSeed()); - } - - try { - DataInputStream in; - synchronized (sLock) { - in = getUrandomInputStream(); - } - synchronized (in) { - in.readFully(bytes); - } - } catch (IOException e) { - throw new SecurityException( - "Failed to read from " + URANDOM_FILE, e); - } - } - - @Override - protected byte[] engineGenerateSeed(int size) { - byte[] seed = new byte[size]; - engineNextBytes(seed); - return seed; - } - - private DataInputStream getUrandomInputStream() { - synchronized (sLock) { - if (sUrandomIn == null) { - // NOTE: Consider inserting a BufferedInputStream between - // DataInputStream and FileInputStream if you need higher - // PRNG output performance and can live with future PRNG - // output being pulled into this process prematurely. - try { - sUrandomIn = new DataInputStream( - new FileInputStream(URANDOM_FILE)); - } catch (IOException e) { - throw new SecurityException("Failed to open " - + URANDOM_FILE + " for reading", e); - } - } - return sUrandomIn; - } - } - - private OutputStream getUrandomOutputStream() throws IOException { - synchronized (sLock) { - if (sUrandomOut == null) { - sUrandomOut = new FileOutputStream(URANDOM_FILE); - } - return sUrandomOut; - } - } - } - - /** - * Generates a device- and invocation-specific seed to be mixed into the - * Linux PRNG. - */ - private static byte[] generateSeed() { - try { - ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); - DataOutputStream seedBufferOut = - new DataOutputStream(seedBuffer); - seedBufferOut.writeLong(System.currentTimeMillis()); - seedBufferOut.writeLong(System.nanoTime()); - seedBufferOut.writeInt(Process.myPid()); - seedBufferOut.writeInt(Process.myUid()); - seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); - seedBufferOut.close(); - return seedBuffer.toByteArray(); - } catch (IOException e) { - throw new SecurityException("Failed to generate seed", e); - } - } - - /** - * Gets the hardware serial number of this device. - * - * @return serial number or {@code null} if not available. - */ - private static String getDeviceSerialNumber() { - // We're using the Reflection API because Build.SERIAL is only available - // since API Level 9 (Gingerbread, Android 2.3). - try { - return (String) Build.class.getField("SERIAL").get(null); - } catch (Exception ignored) { - return null; - } - } - - private static byte[] getBuildFingerprintAndDeviceSerial() { - StringBuilder result = new StringBuilder(); - String fingerprint = Build.FINGERPRINT; - if (fingerprint != null) { - result.append(fingerprint); - } - String serial = getDeviceSerialNumber(); - if (serial != null) { - result.append(serial); - } - try { - return result.toString().getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 encoding not supported"); - } - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java deleted file mode 100644 index 216f4261..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package se.leap.bitmaskclient; - -import java.io.Serializable; -import java.util.Arrays; -import java.util.Locale; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.content.Context; -import android.app.Activity; -import android.content.SharedPreferences; - -/** - * @author Sean Leonard - * - */ -public final class Provider implements Serializable { - - private static final long serialVersionUID = 6003835972151761353L; - - private static Provider instance = null; - - // We'll access our preferences here - private static SharedPreferences preferences = null; - // Represents our Provider's provider.json - private static JSONObject definition = null; - - final public static String - API_URL = "api_uri", - API_VERSION = "api_version", - ALLOW_REGISTRATION = "allow_registration", - API_RETURN_SERIAL = "serial", - SERVICE = "service", - KEY = "provider", - CA_CERT = "ca_cert", - NAME = "name", - DESCRIPTION = "description", - DOMAIN = "domain", - MAIN_URL = "main_url", - DOT_JSON_URL = "provider_json_url" - ; - - // Array of what API versions we understand - protected static final String[] API_VERSIONS = {"1"}; // I assume we might encounter arbitrary version "numbers" - // Some API pieces we want to know about - private static final String API_TERM_SERVICES = "services"; - private static final String API_TERM_NAME = "name"; - private static final String API_TERM_DOMAIN = "domain"; - private static final String API_TERM_DEFAULT_LANGUAGE = "default_language"; - protected static final String[] API_EIP_TYPES = {"openvpn"}; - - private static final String PREFS_EIP_NAME = null; - - - - // What, no individual fields?! We're going to gamble on org.json.JSONObject and JSONArray - // Supporting multiple API versions will probably break this paradigm, - // Forcing me to write a real constructor and rewrite getters/setters - // Also will refactor if i'm instantiating the same local variables all the time - - /** - * - */ - private Provider() {} - - protected static Provider getInstance(){ - if(instance==null){ - instance = new Provider(); - } - return instance; - } - - protected void init(Activity activity) { - - // Load our preferences from SharedPreferences - // If there's nothing there, we will end up returning a rather empty object - // to whoever called getInstance() and they can run the First Run Wizard - //preferences = context.getgetPreferences(0); // 0 == MODE_PRIVATE, but we don't extend Android's classes... - - // Load SharedPreferences - preferences = activity.getSharedPreferences(Dashboard.SHARED_PREFERENCES,Context.MODE_PRIVATE); - // Inflate our provider.json data - try { - definition = new JSONObject( preferences.getString(Provider.KEY, "") ); - } catch (JSONException e) { - // TODO: handle exception - - // FIXME!! We want "real" data!! - } - } - - protected String getDomain(){ - String domain = "Null"; - try { - domain = definition.getString(API_TERM_DOMAIN); - } catch (JSONException e) { - domain = "Null"; - e.printStackTrace(); - } - return domain; - } - - protected String getName(){ - // Should we pass the locale in, or query the system here? - String lang = Locale.getDefault().getLanguage(); - String name = "Null"; // Should it actually /be/ null, for error conditions? - try { - name = definition.getJSONObject(API_TERM_NAME).getString(lang); - } catch (JSONException e) { - // TODO: Nesting try/catch blocks? Crazy - // Maybe you should actually handle exception? - try { - name = definition.getJSONObject(API_TERM_NAME).getString( definition.getString(API_TERM_DEFAULT_LANGUAGE) ); - } catch (JSONException e2) { - // TODO: Will you handle the exception already? - } - } - - return name; - } - - protected String getDescription(){ - String lang = Locale.getDefault().getLanguage(); - String desc = null; - try { - desc = definition.getJSONObject("description").getString(lang); - } catch (JSONException e) { - // TODO: handle exception!! - try { - desc = definition.getJSONObject("description").getString( definition.getString("default_language") ); - } catch (JSONException e2) { - // TODO: i can't believe you're doing it again! - } - } - - return desc; - } - - protected boolean hasEIP() { - JSONArray services = null; - try { - services = definition.getJSONArray(API_TERM_SERVICES); // returns ["openvpn"] - } catch (Exception e) { - // TODO: handle exception - } - for (int i=0;i. - */ - package se.leap.bitmaskclient; - -import java.io.DataOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.net.CookieHandler; -import java.net.CookieManager; -import java.net.CookiePolicy; -import java.net.MalformedURLException; -import java.net.SocketTimeoutException; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLEncoder; -import java.net.UnknownHostException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPrivateKey; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Scanner; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import org.apache.http.client.ClientProtocolException; -import org.jboss.security.srp.SRPParameters; -import org.json.JSONException; -import org.json.JSONObject; - -import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.ProviderListContent.ProviderItem; -import android.app.IntentService; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.ResultReceiver; -import android.util.Base64; -import android.util.Log; - - -/** - * Implements HTTP api methods used to manage communications with the provider server. - * - * It's an IntentService because it downloads data from the Internet, so it operates in the background. - * - * @author parmegv - * @author MeanderingCode - * - */ -public class ProviderAPI extends IntentService { - - private Handler mHandler; - - final public static String - SET_UP_PROVIDER = "setUpProvider", - DOWNLOAD_NEW_PROVIDER_DOTJSON = "downloadNewProviderDotJSON", - SRP_REGISTER = "srpRegister", - SRP_AUTH = "srpAuth", - LOG_OUT = "logOut", - DOWNLOAD_CERTIFICATE = "downloadUserAuthedCertificate", - PARAMETERS = "parameters", - RESULT_KEY = "result", - RECEIVER_KEY = "receiver", - SESSION_ID_COOKIE_KEY = "session_id_cookie_key", - SESSION_ID_KEY = "session_id", - ERRORS = "errors", - UPDATE_PROGRESSBAR = "update_progressbar", - CURRENT_PROGRESS = "current_progress", - TAG = "provider_api_tag" - ; - - final public static int - CUSTOM_PROVIDER_ADDED = 0, - SRP_AUTHENTICATION_SUCCESSFUL = 3, - SRP_AUTHENTICATION_FAILED = 4, - SRP_REGISTRATION_SUCCESSFUL = 5, - SRP_REGISTRATION_FAILED = 6, - LOGOUT_SUCCESSFUL = 7, - LOGOUT_FAILED = 8, - CORRECTLY_DOWNLOADED_CERTIFICATE = 9, - INCORRECTLY_DOWNLOADED_CERTIFICATE = 10, - PROVIDER_OK = 11, - PROVIDER_NOK = 12, - CORRECTLY_DOWNLOADED_ANON_CERTIFICATE = 13, - INCORRECTLY_DOWNLOADED_ANON_CERTIFICATE = 14 - ; - - private static boolean - CA_CERT_DOWNLOADED = false, - PROVIDER_JSON_DOWNLOADED = false, - EIP_SERVICE_JSON_DOWNLOADED = false - ; - - private static String last_provider_main_url; - private static boolean last_danger_on = false; - private static boolean setting_up_provider = true; - - public static void stop() { - setting_up_provider = false; - } - - public ProviderAPI() { - super("ProviderAPI"); - Log.v("ClassName", "Provider API"); - } - - @Override - public void onCreate() { - super.onCreate(); - mHandler = new Handler(); - CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER) ); - } - - public static String lastProviderMainUrl() { - return last_provider_main_url; - } - - public static boolean lastDangerOn() { - return last_danger_on; - } - - private String formatErrorMessage(final int toast_string_id) { - return "{ \"" + ERRORS + "\" : \""+getResources().getString(toast_string_id)+"\" }"; - } - - @Override - protected void onHandleIntent(Intent command) { - final ResultReceiver receiver = command.getParcelableExtra(RECEIVER_KEY); - String action = command.getAction(); - Bundle parameters = command.getBundleExtra(PARAMETERS); - setting_up_provider = true; - - if(action.equalsIgnoreCase(SET_UP_PROVIDER)) { - Bundle result = setUpProvider(parameters); - if(setting_up_provider) { - if(result.getBoolean(RESULT_KEY)) { - receiver.send(PROVIDER_OK, result); - } else { - receiver.send(PROVIDER_NOK, result); - } - } - } else if (action.equalsIgnoreCase(SRP_AUTH)) { - Bundle session_id_bundle = authenticateBySRP(parameters); - if(session_id_bundle.getBoolean(RESULT_KEY)) { - receiver.send(SRP_AUTHENTICATION_SUCCESSFUL, session_id_bundle); - } else { - receiver.send(SRP_AUTHENTICATION_FAILED, session_id_bundle); - } - } else if (action.equalsIgnoreCase(LOG_OUT)) { - if(logOut(parameters)) { - receiver.send(LOGOUT_SUCCESSFUL, Bundle.EMPTY); - } else { - receiver.send(LOGOUT_FAILED, Bundle.EMPTY); - } - } else if (action.equalsIgnoreCase(DOWNLOAD_CERTIFICATE)) { - if(getNewCert(parameters)) { - receiver.send(CORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); - } else { - receiver.send(INCORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); - } - } - } - - /** - * Starts the authentication process using SRP protocol. - * - * @param task containing: username, password and api url. - * @return a bundle with a boolean value mapped to a key named RESULT_KEY, and which is true if authentication was successful. - */ - private Bundle authenticateBySRP(Bundle task) { - Bundle session_id_bundle = new Bundle(); - int progress = 0; - - String username = (String) task.get(LogInDialog.USERNAME); - String password = (String) task.get(LogInDialog.PASSWORD); - if(validUserLoginData(username, password)) { - - String authentication_server = (String) task.get(Provider.API_URL); - - SRPParameters params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), ConfigHelper.G.toByteArray(), BigInteger.ZERO.toByteArray(), "SHA-256"); - LeapSRPSession client = new LeapSRPSession(username, password, params); - byte[] A = client.exponential(); - broadcast_progress(progress++); - try { - JSONObject saltAndB = sendAToSRPServer(authentication_server, username, new BigInteger(1, A).toString(16)); - if(saltAndB.length() > 0) { - String salt = saltAndB.getString(LeapSRPSession.SALT); - broadcast_progress(progress++); - byte[] Bbytes = new BigInteger(saltAndB.getString("B"), 16).toByteArray(); - byte[] M1 = client.response(new BigInteger(salt, 16).toByteArray(), Bbytes); - if(M1 != null) { - broadcast_progress(progress++); - JSONObject session_idAndM2 = sendM1ToSRPServer(authentication_server, username, M1); - if(session_idAndM2.has(LeapSRPSession.M2) && client.verify((byte[])session_idAndM2.get(LeapSRPSession.M2))) { - session_id_bundle.putBoolean(RESULT_KEY, true); - broadcast_progress(progress++); - } else { - session_id_bundle.putBoolean(RESULT_KEY, false); - session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_bad_user_password_user_message)); - session_id_bundle.putString(LogInDialog.USERNAME, username); - } - } else { - session_id_bundle.putBoolean(RESULT_KEY, false); - session_id_bundle.putString(LogInDialog.USERNAME, username); - session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_srp_math_error_user_message)); - } - broadcast_progress(progress++); - } else { - session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_bad_user_password_user_message)); - session_id_bundle.putString(LogInDialog.USERNAME, username); - session_id_bundle.putBoolean(RESULT_KEY, false); - } - } catch (ClientProtocolException e) { - session_id_bundle.putBoolean(RESULT_KEY, false); - session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_client_http_user_message)); - session_id_bundle.putString(LogInDialog.USERNAME, username); - } catch (IOException e) { - session_id_bundle.putBoolean(RESULT_KEY, false); - session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_io_exception_user_message)); - session_id_bundle.putString(LogInDialog.USERNAME, username); - } catch (JSONException e) { - session_id_bundle.putBoolean(RESULT_KEY, false); - session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_json_exception_user_message)); - session_id_bundle.putString(LogInDialog.USERNAME, username); - } catch (NoSuchAlgorithmException e) { - session_id_bundle.putBoolean(RESULT_KEY, false); - session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_no_such_algorithm_exception_user_message)); - session_id_bundle.putString(LogInDialog.USERNAME, username); - } catch (KeyManagementException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (KeyStoreException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (CertificateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } else { - if(!wellFormedPassword(password)) { - session_id_bundle.putBoolean(RESULT_KEY, false); - session_id_bundle.putString(LogInDialog.USERNAME, username); - session_id_bundle.putBoolean(LogInDialog.PASSWORD_INVALID_LENGTH, true); - } - if(username.isEmpty()) { - session_id_bundle.putBoolean(RESULT_KEY, false); - session_id_bundle.putBoolean(LogInDialog.USERNAME_MISSING, true); - } - } - - return session_id_bundle; - } - - /** - * Sets up an intent with the progress value passed as a parameter - * and sends it as a broadcast. - * @param progress - */ - private void broadcast_progress(int progress) { - Intent intentUpdate = new Intent(); - intentUpdate.setAction(UPDATE_PROGRESSBAR); - intentUpdate.addCategory(Intent.CATEGORY_DEFAULT); - intentUpdate.putExtra(CURRENT_PROGRESS, progress); - sendBroadcast(intentUpdate); - } - - /** - * Validates parameters entered by the user to log in - * @param entered_username - * @param entered_password - * @return true if both parameters are present and the entered password length is greater or equal to eight (8). - */ - private boolean validUserLoginData(String entered_username, String entered_password) { - return !(entered_username.isEmpty()) && wellFormedPassword(entered_password); - } - - /** - * Validates a password - * @param entered_password - * @return true if the entered password length is greater or equal to eight (8). - */ - private boolean wellFormedPassword(String entered_password) { - return entered_password.length() >= 8; - } - - /** - * Sends an HTTP POST request to the authentication server with the SRP Parameter A. - * @param server_url - * @param username - * @param clientA First SRP parameter sent - * @return response from authentication server - * @throws ClientProtocolException - * @throws IOException - * @throws JSONException - * @throws CertificateException - * @throws NoSuchAlgorithmException - * @throws KeyStoreException - * @throws KeyManagementException - */ - private JSONObject sendAToSRPServer(String server_url, String username, String clientA) throws ClientProtocolException, IOException, JSONException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException { - Map parameters = new HashMap(); - parameters.put("login", username); - parameters.put("A", clientA); - return sendToServer(server_url + "/sessions.json", "POST", parameters); - - /*HttpPost post = new HttpPost(server_url + "/sessions.json" + "?" + "login=" + username + "&&" + "A=" + clientA); - return sendToServer(post);*/ - } - - /** - * Sends an HTTP PUT request to the authentication server with the SRP Parameter M1 (or simply M). - * @param server_url - * @param username - * @param m1 Second SRP parameter sent - * @return response from authentication server - * @throws ClientProtocolException - * @throws IOException - * @throws JSONException - * @throws CertificateException - * @throws NoSuchAlgorithmException - * @throws KeyStoreException - * @throws KeyManagementException - */ - private JSONObject sendM1ToSRPServer(String server_url, String username, byte[] m1) throws ClientProtocolException, IOException, JSONException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException { - Map parameters = new HashMap(); - parameters.put("client_auth", new BigInteger(1, ConfigHelper.trim(m1)).toString(16)); - - //HttpPut put = new HttpPut(server_url + "/sessions/" + username +".json" + "?" + "client_auth" + "=" + new BigInteger(1, ConfigHelper.trim(m1)).toString(16)); - JSONObject json_response = sendToServer(server_url + "/sessions/" + username +".json", "PUT", parameters); - - JSONObject session_idAndM2 = new JSONObject(); - if(json_response.length() > 0) { - byte[] M2_not_trimmed = new BigInteger(json_response.getString(LeapSRPSession.M2), 16).toByteArray(); - /*Cookie session_id_cookie = LeapHttpClient.getInstance(getApplicationContext()).getCookieStore().getCookies().get(0); - session_idAndM2.put(ConfigHelper.SESSION_ID_COOKIE_KEY, session_id_cookie.getName()); - session_idAndM2.put(ConfigHelper.SESSION_ID_KEY, session_id_cookie.getValue());*/ - session_idAndM2.put(LeapSRPSession.M2, ConfigHelper.trim(M2_not_trimmed)); - CookieHandler.setDefault(null); // we don't need cookies anymore - String token = json_response.getString(LeapSRPSession.TOKEN); - LeapSRPSession.setToken(token); - } - return session_idAndM2; - } - - /** - * Executes an HTTP request expecting a JSON response. - * @param url - * @param request_method - * @param parameters - * @return response from authentication server - * @throws IOException - * @throws JSONException - * @throws MalformedURLException - * @throws CertificateException - * @throws NoSuchAlgorithmException - * @throws KeyStoreException - * @throws KeyManagementException - */ - private JSONObject sendToServer(String url, String request_method, Map parameters) throws JSONException, MalformedURLException, IOException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException { - JSONObject json_response; - InputStream is = null; - HttpsURLConnection urlConnection = (HttpsURLConnection)new URL(url).openConnection(); - urlConnection.setRequestMethod(request_method); - urlConnection.setChunkedStreamingMode(0); - urlConnection.setSSLSocketFactory(getProviderSSLSocketFactory()); - try { - - DataOutputStream writer = new DataOutputStream(urlConnection.getOutputStream()); - writer.writeBytes(formatHttpParameters(parameters)); - writer.close(); - - is = urlConnection.getInputStream(); - String plain_response = new Scanner(is).useDelimiter("\\A").next(); - json_response = new JSONObject(plain_response); - } finally { - InputStream error_stream = urlConnection.getErrorStream(); - if(error_stream != null) { - String error_response = new Scanner(error_stream).useDelimiter("\\A").next(); - urlConnection.disconnect(); - Log.d("Error", error_response); - json_response = new JSONObject(error_response); - if(!json_response.isNull(ERRORS) || json_response.has(ERRORS)) { - return new JSONObject(); - } - } - } - - return json_response; - } - - private String formatHttpParameters(Map parameters) throws UnsupportedEncodingException { - StringBuilder result = new StringBuilder(); - boolean first = true; - - Iterator parameter_iterator = parameters.keySet().iterator(); - while(parameter_iterator.hasNext()) { - if(first) - first = false; - else - result.append("&&"); - - String key = parameter_iterator.next(); - String value = parameters.get(key); - - result.append(URLEncoder.encode(key, "UTF-8")); - result.append("="); - result.append(URLEncoder.encode(value, "UTF-8")); - } - - return result.toString(); - } - - - - - /** - * Downloads a provider.json from a given URL, adding a new provider using the given name. - * @param task containing a boolean meaning if the provider is custom or not, another boolean meaning if the user completely trusts this provider, the provider name and its provider.json url. - * @return a bundle with a boolean value mapped to a key named RESULT_KEY, and which is true if the update was successful. - */ - private Bundle setUpProvider(Bundle task) { - int progress = 0; - Bundle current_download = new Bundle(); - - if(task != null && task.containsKey(ProviderItem.DANGER_ON) && task.containsKey(Provider.MAIN_URL)) { - last_danger_on = task.getBoolean(ProviderItem.DANGER_ON); - last_provider_main_url = task.getString(Provider.MAIN_URL); - CA_CERT_DOWNLOADED = PROVIDER_JSON_DOWNLOADED = EIP_SERVICE_JSON_DOWNLOADED = false; - } - - if(!CA_CERT_DOWNLOADED) - current_download = downloadCACert(last_provider_main_url, last_danger_on); - if(CA_CERT_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) { - broadcast_progress(progress++); - CA_CERT_DOWNLOADED = true; - if(!PROVIDER_JSON_DOWNLOADED) - current_download = getAndSetProviderJson(last_provider_main_url); - if(PROVIDER_JSON_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) { - broadcast_progress(progress++); - PROVIDER_JSON_DOWNLOADED = true; - current_download = getAndSetEipServiceJson(); - if(current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY)) { - broadcast_progress(progress++); - EIP_SERVICE_JSON_DOWNLOADED = true; - } - } - } - - return current_download; - } - - private Bundle downloadCACert(String provider_main_url, boolean danger_on) { - Bundle result = new Bundle(); - String cert_string = downloadWithCommercialCA(provider_main_url + "/ca.crt", danger_on); - - if(validCertificate(cert_string) && setting_up_provider) { - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(Provider.CA_CERT, cert_string).commit(); - result.putBoolean(RESULT_KEY, true); - } else { - String reason_to_fail = pickErrorMessage(cert_string); - result.putString(ERRORS, reason_to_fail); - result.putBoolean(RESULT_KEY, false); - } - - return result; - } - - - public static boolean caCertDownloaded() { - return CA_CERT_DOWNLOADED; - } - - private boolean validCertificate(String cert_string) { - boolean result = false; - if(!ConfigHelper.checkErroneousDownload(cert_string)) { - X509Certificate certCert = ConfigHelper.parseX509CertificateFromString(cert_string); - try { - Base64.encodeToString( certCert.getEncoded(), Base64.DEFAULT); - result = true; - } catch (CertificateEncodingException e) { - Log.d(TAG, e.getLocalizedMessage()); - } - } - - return result; - } - - private Bundle getAndSetProviderJson(String provider_main_url) { - Bundle result = new Bundle(); - - if(setting_up_provider) { - String provider_dot_json_string = downloadWithProviderCA(provider_main_url + "/provider.json", true); - - try { - JSONObject provider_json = new JSONObject(provider_dot_json_string); - String name = provider_json.getString(Provider.NAME); - //TODO setProviderName(name); - - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(Provider.KEY, provider_json.toString()).commit(); - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(EIP.ALLOWED_ANON, provider_json.getJSONObject(Provider.SERVICE).getBoolean(EIP.ALLOWED_ANON)).commit(); - - result.putBoolean(RESULT_KEY, true); - } catch (JSONException e) { - //TODO Error message should be contained in that provider_dot_json_string - String reason_to_fail = pickErrorMessage(provider_dot_json_string); - result.putString(ERRORS, reason_to_fail); - result.putBoolean(RESULT_KEY, false); - } - } - return result; - } - - - - public static boolean providerJsonDownloaded() { - return PROVIDER_JSON_DOWNLOADED; - } - - private Bundle getAndSetEipServiceJson() { - Bundle result = new Bundle(); - String eip_service_json_string = ""; - if(setting_up_provider) { - try { - JSONObject provider_json = new JSONObject(getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.KEY, "")); - String eip_service_url = provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION) + "/" + EIP.SERVICE_API_PATH; - eip_service_json_string = downloadWithProviderCA(eip_service_url, true); - JSONObject eip_service_json = new JSONObject(eip_service_json_string); - eip_service_json.getInt(Provider.API_RETURN_SERIAL); - - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(EIP.KEY, eip_service_json.toString()).commit(); - - result.putBoolean(RESULT_KEY, true); - } catch (JSONException e) { - String reason_to_fail = pickErrorMessage(eip_service_json_string); - result.putString(ERRORS, reason_to_fail); - result.putBoolean(RESULT_KEY, false); - } - } - return result; - } - - public static boolean eipServiceDownloaded() { - return EIP_SERVICE_JSON_DOWNLOADED; - } - - /** - * Interprets the error message as a JSON object and extract the "errors" keyword pair. - * If the error message is not a JSON object, then it is returned untouched. - * @param string_json_error_message - * @return final error message - */ - private String pickErrorMessage(String string_json_error_message) { - String error_message = ""; - try { - JSONObject json_error_message = new JSONObject(string_json_error_message); - error_message = json_error_message.getString(ERRORS); - } catch (JSONException e) { - // TODO Auto-generated catch block - error_message = string_json_error_message; - } - - return error_message; - } - - /** - * Tries to download the contents of the provided url using commercially validated CA certificate from chosen provider. - * - * If danger_on flag is true, SSL exceptions will be managed by futher methods that will try to use some bypass methods. - * @param string_url - * @param danger_on if the user completely trusts this provider - * @return - */ - private String downloadWithCommercialCA(String string_url, boolean danger_on) { - - String json_file_content = ""; - - URL provider_url = null; - int seconds_of_timeout = 1; - try { - provider_url = new URL(string_url); - URLConnection url_connection = provider_url.openConnection(); - url_connection.setConnectTimeout(seconds_of_timeout*1000); - if(!LeapSRPSession.getToken().isEmpty()) - url_connection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token = " + LeapSRPSession.getToken()); - json_file_content = new Scanner(url_connection.getInputStream()).useDelimiter("\\A").next(); - } catch (MalformedURLException e) { - json_file_content = formatErrorMessage(R.string.malformed_url); - } catch(SocketTimeoutException e) { - json_file_content = formatErrorMessage(R.string.server_unreachable_message); - } catch (IOException e) { - if(provider_url != null) { - json_file_content = downloadWithProviderCA(string_url, danger_on); - } else { - json_file_content = formatErrorMessage(R.string.certificate_error); - } - } catch (Exception e) { - if(provider_url != null && danger_on) { - json_file_content = downloadWithProviderCA(string_url, danger_on); - } - } - - return json_file_content; - } - - /** - * Tries to download the contents of the provided url using not commercially validated CA certificate from chosen provider. - * @param url as a string - * @param danger_on true to download CA certificate in case it has not been downloaded. - * @return an empty string if it fails, the url content if not. - */ - private String downloadWithProviderCA(String url_string, boolean danger_on) { - String json_file_content = ""; - - try { - URL url = new URL(url_string); - // Tell the URLConnection to use a SocketFactory from our SSLContext - HttpsURLConnection urlConnection = - (HttpsURLConnection)url.openConnection(); - urlConnection.setSSLSocketFactory(getProviderSSLSocketFactory()); - if(!LeapSRPSession.getToken().isEmpty()) - urlConnection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token=" + LeapSRPSession.getToken()); - json_file_content = new Scanner(urlConnection.getInputStream()).useDelimiter("\\A").next(); - } catch (CertificateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (UnknownHostException e) { - json_file_content = formatErrorMessage(R.string.server_unreachable_message); - } catch (IOException e) { - // The downloaded certificate doesn't validate our https connection. - if(danger_on) { - json_file_content = downloadWithoutCA(url_string); - } else { - json_file_content = formatErrorMessage(R.string.certificate_error); - } - } catch (KeyStoreException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (NoSuchAlgorithmException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (KeyManagementException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return json_file_content; - } - - private javax.net.ssl.SSLSocketFactory getProviderSSLSocketFactory() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException { - String provider_cert_string = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.CA_CERT,""); - - java.security.cert.Certificate provider_certificate = ConfigHelper.parseX509CertificateFromString(provider_cert_string); - - // Create a KeyStore containing our trusted CAs - String keyStoreType = KeyStore.getDefaultType(); - KeyStore keyStore = KeyStore.getInstance(keyStoreType); - keyStore.load(null, null); - keyStore.setCertificateEntry("provider_ca_certificate", provider_certificate); - - // Create a TrustManager that trusts the CAs in our KeyStore - String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); - TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); - tmf.init(keyStore); - - // Create an SSLContext that uses our TrustManager - SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, tmf.getTrustManagers(), null); - - return context.getSocketFactory(); - } - - /** - * Downloads the string that's in the url with any certificate. - */ - private String downloadWithoutCA(String url_string) { - String string = ""; - try { - - HostnameVerifier hostnameVerifier = new HostnameVerifier() { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - }; - - class DefaultTrustManager implements X509TrustManager { - - @Override - public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} - - @Override - public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} - - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - } - - SSLContext context = SSLContext.getInstance("TLS"); - context.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom()); - - URL url = new URL(url_string); - HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); - urlConnection.setSSLSocketFactory(context.getSocketFactory()); - urlConnection.setHostnameVerifier(hostnameVerifier); - string = new Scanner(urlConnection.getInputStream()).useDelimiter("\\A").next(); - System.out.println("String ignoring certificate = " + string); - } catch (FileNotFoundException e) { - e.printStackTrace(); - string = formatErrorMessage(R.string.server_unreachable_message); - } catch (IOException e) { - // The downloaded certificate doesn't validate our https connection. - e.printStackTrace(); - string = formatErrorMessage(R.string.certificate_error); - } catch (NoSuchAlgorithmException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (KeyManagementException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return string; - } - - /** - * Logs out from the api url retrieved from the task. - * @param task containing api url from which the user will log out - * @return true if there were no exceptions - */ - private boolean logOut(Bundle task) { - try { - String delete_url = task.getString(Provider.API_URL) + "/logout"; - int progress = 0; - - HttpsURLConnection urlConnection = (HttpsURLConnection)new URL(delete_url).openConnection(); - urlConnection.setRequestMethod("DELETE"); - urlConnection.setSSLSocketFactory(getProviderSSLSocketFactory()); - - int responseCode = urlConnection.getResponseCode(); - broadcast_progress(progress++); - LeapSRPSession.setToken(""); - Log.d(TAG, Integer.toString(responseCode)); - } catch (ClientProtocolException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } catch (IndexOutOfBoundsException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } catch (KeyManagementException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } 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(); - } - return true; - } - - /** - * Downloads a new OpenVPN certificate, attaching authenticated cookie for authenticated certificate. - * - * @param task containing the type of the certificate to be downloaded - * @return true if certificate was downloaded correctly, false if provider.json or danger_on flag are not present in SharedPreferences, or if the certificate url could not be parsed as a URI, or if there was an SSL error. - */ - private boolean getNewCert(Bundle task) { - - try { - String type_of_certificate = task.getString(ConfigurationWizard.TYPE_OF_CERTIFICATE); - JSONObject provider_json = new JSONObject(getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.KEY, "")); - - String provider_main_url = provider_json.getString(Provider.API_URL); - URL new_cert_string_url = new URL(provider_main_url + "/" + provider_json.getString(Provider.API_VERSION) + "/" + EIP.CERTIFICATE); - - boolean danger_on = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getBoolean(ProviderItem.DANGER_ON, false); - - String cert_string = downloadWithProviderCA(new_cert_string_url.toString(), danger_on); - - if(!cert_string.isEmpty()) { - if(ConfigHelper.checkErroneousDownload(cert_string)) { - String reason_to_fail = provider_json.getString(ERRORS); - //result.putString(ConfigHelper.ERRORS_KEY, reason_to_fail); - //result.putBoolean(ConfigHelper.RESULT_KEY, false); - return false; - } else { - - // API returns concatenated cert & key. Split them for OpenVPN options - String certificateString = null, keyString = null; - String[] certAndKey = cert_string.split("(?<=-\n)"); - for (int i=0; i < certAndKey.length-1; i++){ - if ( certAndKey[i].contains("KEY") ) { - keyString = certAndKey[i++] + certAndKey[i]; - } - else if ( certAndKey[i].contains("CERTIFICATE") ) { - certificateString = certAndKey[i++] + certAndKey[i]; - } - } - try { - RSAPrivateKey keyCert = ConfigHelper.parseRsaKeyFromString(keyString); - keyString = Base64.encodeToString( keyCert.getEncoded(), Base64.DEFAULT ); - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(EIP.PRIVATE_KEY, "-----BEGIN RSA PRIVATE KEY-----\n"+keyString+"-----END RSA PRIVATE KEY-----").commit(); - - X509Certificate certCert = ConfigHelper.parseX509CertificateFromString(certificateString); - certificateString = Base64.encodeToString( certCert.getEncoded(), Base64.DEFAULT); - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(EIP.CERTIFICATE, "-----BEGIN CERTIFICATE-----\n"+certificateString+"-----END CERTIFICATE-----").commit(); - - return true; - } catch (CertificateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } - } - } else { - return false; - } - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } /*catch (URISyntaxException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - }*/ - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderAPIResultReceiver.java b/app/src/main/java/se/leap/bitmaskclient/ProviderAPIResultReceiver.java deleted file mode 100644 index 7b256124..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderAPIResultReceiver.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package se.leap.bitmaskclient; - -import android.os.Bundle; -import android.os.Handler; -import android.os.ResultReceiver; - -/** - * Implements the ResultReceiver needed by Activities using ProviderAPI to receive the results of its operations. - * @author parmegv - * - */ -public class ProviderAPIResultReceiver extends ResultReceiver { - private Receiver mReceiver; - - public ProviderAPIResultReceiver(Handler handler) { - super(handler); - // TODO Auto-generated constructor stub - } - - public void setReceiver(Receiver receiver) { - mReceiver = receiver; - } - - /** - * Interface to enable ProviderAPIResultReceiver to receive results from the ProviderAPI IntentService. - * @author parmegv - * - */ - public interface Receiver { - public void onReceiveResult(int resultCode, Bundle resultData); - } - - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - if (mReceiver != null) { - mReceiver.onReceiveResult(resultCode, resultData); - } - } - -} diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderDetailFragment.java b/app/src/main/java/se/leap/bitmaskclient/ProviderDetailFragment.java deleted file mode 100644 index c067ce2b..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderDetailFragment.java +++ /dev/null @@ -1,115 +0,0 @@ -package se.leap.bitmaskclient; - -import org.json.JSONException; -import org.json.JSONObject; - -import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.ProviderListContent.ProviderItem; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; - -public class ProviderDetailFragment extends DialogFragment { - - final public static String TAG = "providerDetailFragment"; - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - try { - - LayoutInflater inflater = getActivity().getLayoutInflater(); - View provider_detail_view = inflater.inflate(R.layout.provider_detail_fragment, null); - - JSONObject provider_json = new JSONObject(getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, getActivity().MODE_PRIVATE).getString(Provider.KEY, "")); - - final TextView domain = (TextView)provider_detail_view.findViewById(R.id.provider_detail_domain); - domain.setText(provider_json.getString(Provider.DOMAIN)); - final TextView name = (TextView)provider_detail_view.findViewById(R.id.provider_detail_name); - name.setText(provider_json.getJSONObject(Provider.NAME).getString("en")); - final TextView description = (TextView)provider_detail_view.findViewById(R.id.provider_detail_description); - description.setText(provider_json.getJSONObject(Provider.DESCRIPTION).getString("en")); - - builder.setView(provider_detail_view); - builder.setTitle(R.string.provider_details_fragment_title); - - if(anon_allowed(provider_json)) { - builder.setPositiveButton(R.string.use_anonymously_button, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - interface_with_configuration_wizard.use_anonymously(); - } - }); - } - - if(registration_allowed(provider_json)) { - builder.setNegativeButton(R.string.login_button, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - interface_with_configuration_wizard.login(); - } - }); - } - - return builder.create(); - } catch (JSONException e) { - return null; - } - } - - private boolean anon_allowed(JSONObject provider_json) { - try { - JSONObject service_description = provider_json.getJSONObject(Provider.SERVICE); - return service_description.has(EIP.ALLOWED_ANON) && service_description.getBoolean(EIP.ALLOWED_ANON); - } catch (JSONException e) { - return false; - } - } - - private boolean registration_allowed(JSONObject provider_json) { - try { - JSONObject service_description = provider_json.getJSONObject(Provider.SERVICE); - return service_description.has(Provider.ALLOW_REGISTRATION) && service_description.getBoolean(Provider.ALLOW_REGISTRATION); - } catch (JSONException e) { - return false; - } - } - - @Override - public void onCancel(DialogInterface dialog) { - super.onCancel(dialog); - SharedPreferences.Editor editor = getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit(); - editor.remove(Provider.KEY).remove(ProviderItem.DANGER_ON).remove(EIP.ALLOWED_ANON).remove(EIP.KEY).commit(); - interface_with_configuration_wizard.showAllProviders(); - } - - public static DialogFragment newInstance() { - ProviderDetailFragment provider_detail_fragment = new ProviderDetailFragment(); - return provider_detail_fragment; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - interface_with_configuration_wizard = (ProviderDetailFragmentInterface) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement LogInDialogListener"); - } - } - - public interface ProviderDetailFragmentInterface { - public void login(); - public void use_anonymously(); - public void showAllProviders(); - } - - ProviderDetailFragmentInterface interface_with_configuration_wizard; -} diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java deleted file mode 100644 index 43bba085..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java +++ /dev/null @@ -1,114 +0,0 @@ -package se.leap.bitmaskclient; - -import java.util.List; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TwoLineListItem; - -public class ProviderListAdapter extends ArrayAdapter { - private static boolean[] hidden = null; - - public void hide(int position) { - hidden[getRealPosition(position)] = true; - notifyDataSetChanged(); - notifyDataSetInvalidated(); - } - - public void unHide(int position) { - hidden[getRealPosition(position)] = false; - notifyDataSetChanged(); - notifyDataSetInvalidated(); - } - - public void unHideAll() { - for (int provider_index = 0; provider_index < hidden.length; provider_index++) - hidden[provider_index] = false; - } - - private int getRealPosition(int position) { - int hElements = getHiddenCountUpTo(position); - int diff = 0; - for(int i=0;i objects) { - super(mContext, layout, objects); - if(hidden == null) { - hidden = new boolean[objects.size()]; - for (int i = 0; i < objects.size(); i++) - hidden[i] = false; - } - } - - public ProviderListAdapter(Context mContext, int layout, List objects, boolean show_all_providers) { - super(mContext, layout, objects); - if(show_all_providers) { - hidden = new boolean[objects.size()]; - for (int i = 0; i < objects.size(); i++) - hidden[i] = false; - } - } - - @Override - public void add(T item) { - super.add(item); - boolean[] new_hidden = new boolean[hidden.length+1]; - System.arraycopy(hidden, 0, new_hidden, 0, hidden.length); - new_hidden[hidden.length] = false; - hidden = new_hidden; - } - - @Override - public void remove(T item) { - super.remove(item); - boolean[] new_hidden = new boolean[hidden.length-1]; - System.arraycopy(hidden, 0, new_hidden, 0, hidden.length-1); - hidden = new_hidden; - } - - @Override - public View getView(int index, View convertView, ViewGroup parent) { - TwoLineListItem row; - int position = getRealPosition(index); - if (convertView == null) { - LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - row = (TwoLineListItem)inflater.inflate(R.layout.provider_list_item, null); - } else { - row = (TwoLineListItem)convertView; - } - ProviderListContent.ProviderItem data = ProviderListContent.ITEMS.get(position); - row.getText1().setText(data.domain()); - row.getText2().setText(data.name()); - - return row; - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListContent.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListContent.java deleted file mode 100644 index e1ca4f9a..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderListContent.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package se.leap.bitmaskclient; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.net.URL; -import java.net.MalformedURLException; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Models the provider list shown in the ConfigurationWizard. - * - * @author parmegv - * - */ -public class ProviderListContent { - - public static List ITEMS = new ArrayList(); - - public static Map ITEM_MAP = new HashMap(); - - /** - * Adds a new provider item to the end of the items map, and to the items list. - * @param item - */ - public static void addItem(ProviderItem item) { - ITEMS.add(item); - ITEM_MAP.put(String.valueOf(ITEMS.size()), item); - } - public static void removeItem(ProviderItem item) { - ITEMS.remove(item); - ITEM_MAP.remove(item); - } - - /** - * A provider item. - */ - public static class ProviderItem { - final public static String CUSTOM = "custom"; - final public static String DANGER_ON = "danger_on"; - private String provider_main_url; - private String name; - - /** - * @param name of the provider - * @param urls_file_input_stream file input stream linking with the assets url file - * @param custom if it's a new provider entered by the user or not - * @param danger_on if the user trusts completely the new provider - */ - public ProviderItem(String name, InputStream urls_file_input_stream) { - - try { - byte[] urls_file_bytes = new byte[urls_file_input_stream.available()]; - urls_file_input_stream.read(urls_file_bytes); - String urls_file_content = new String(urls_file_bytes); - JSONObject file_contents = new JSONObject(urls_file_content); - provider_main_url = file_contents.getString(Provider.MAIN_URL); - this.name = name; - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - /** - * @param name of the provider - * @param provider_main_url used to download provider.json file of the provider - * @param provider_json already downloaded - * @param custom if it's a new provider entered by the user or not - */ - public ProviderItem(String name, String provider_main_url) { - this.name = name; - this.provider_main_url = provider_main_url; - } - - public String name() { return name; } - - public String providerMainUrl() { return provider_main_url; } - - public String domain() { - try { - return new URL(provider_main_url).getHost(); - } catch (MalformedURLException e) { - return provider_main_url.replaceFirst("http[s]?://", "").replaceFirst("/.*", ""); - } - } - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java deleted file mode 100644 index db414d87..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package se.leap.bitmaskclient; - -import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.ProviderListContent.ProviderItem; -import android.app.Activity; -import android.app.ListFragment; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListView; - -/** - * A list fragment representing a list of Providers. This fragment - * also supports tablet devices by allowing list items to be given an - * 'activated' state upon selection. This helps indicate which item is - * currently being viewed in a {@link DashboardFragment}. - *

- * Activities containing this fragment MUST implement the {@link Callbacks} - * interface. - */ -public class ProviderListFragment extends ListFragment { - - public static String TAG = "provider_list_fragment"; - public static String SHOW_ALL_PROVIDERS = "show_all_providers"; - public static String TOP_PADDING = "top padding from providerlistfragment"; - private ProviderListAdapter content_adapter; - - /** - * The serialization (saved instance state) Bundle key representing the - * activated item position. Only used on tablets. - */ - private static final String STATE_ACTIVATED_POSITION = "activated_position"; - - /** - * The fragment's current callback object, which is notified of list item - * clicks. - */ - private Callbacks mCallbacks = sDummyCallbacks; - - /** - * The current activated item position. Only used on tablets. - */ - private int mActivatedPosition = ListView.INVALID_POSITION; - - /** - * A callback interface that all activities containing this fragment must - * implement. This mechanism allows activities to be notified of item - * selections. - */ - public interface Callbacks { - /** - * Callback for when an item has been selected. - */ - public void onItemSelected(String id); - } - - /** - * A dummy implementation of the {@link Callbacks} interface that does - * nothing. Used only when this fragment is not attached to an activity. - */ - private static Callbacks sDummyCallbacks = new Callbacks() { - @Override - public void onItemSelected(String id) { - } - }; - - /** - * Mandatory empty constructor for the fragment manager to instantiate the - * fragment (e.g. upon screen orientation changes). - */ - public ProviderListFragment() { - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if(getArguments().containsKey(SHOW_ALL_PROVIDERS)) - content_adapter = new ProviderListAdapter( - getActivity(), - R.layout.provider_list_item, - ProviderListContent.ITEMS, getArguments().getBoolean(SHOW_ALL_PROVIDERS)); - else - content_adapter = new ProviderListAdapter( - getActivity(), - R.layout.provider_list_item, - ProviderListContent.ITEMS); - - - setListAdapter(content_adapter); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { - return inflater.inflate(R.layout.provider_list_fragment, container, false); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - // Restore the previously serialized activated item position. - if (savedInstanceState != null - && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) { - setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION)); - } - if(getArguments() != null && getArguments().containsKey(TOP_PADDING)) { - int topPadding = getArguments().getInt(TOP_PADDING); - View current_view = getView(); - getView().setPadding(current_view.getPaddingLeft(), topPadding, current_view.getPaddingRight(), current_view.getPaddingBottom()); - } - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - // Activities containing this fragment must implement its callbacks. - if (!(activity instanceof Callbacks)) { - throw new IllegalStateException("Activity must implement fragment's callbacks."); - } - - mCallbacks = (Callbacks) activity; - } - - @Override - public void onDetach() { - super.onDetach(); - - // Reset the active callbacks interface to the dummy implementation. - mCallbacks = sDummyCallbacks; - } - - @Override - public void onListItemClick(ListView listView, View view, int position, long id) { - super.onListItemClick(listView, view, position, id); - - // Notify the active callbacks interface (the activity, if the - // fragment is attached to one) that an item has been selected. - mCallbacks.onItemSelected(ProviderListContent.ITEMS.get(position).name()); - - for(int item_position = 0; item_position < listView.getCount(); item_position++) { - if(item_position != position) - content_adapter.hide(item_position); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - if (mActivatedPosition != ListView.INVALID_POSITION) { - // Serialize and persist the activated item position. - outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition); - } - } - - public void notifyAdapter() { - content_adapter.notifyDataSetChanged(); - } - /** - * Turns on activate-on-click mode. When this mode is on, list items will be - * given the 'activated' state when touched. - */ - public void setActivateOnItemClick(boolean activateOnItemClick) { - // When setting CHOICE_MODE_SINGLE, ListView will automatically - // give items the 'activated' state when touched. - getListView().setChoiceMode(activateOnItemClick - ? ListView.CHOICE_MODE_SINGLE - : ListView.CHOICE_MODE_NONE); - } - - private void setActivatedPosition(int position) { - if (position == ListView.INVALID_POSITION) { - getListView().setItemChecked(mActivatedPosition, false); - } else { - getListView().setItemChecked(position, true); - } - - mActivatedPosition = position; - } - - public void removeLastItem() { - unhideAll(); - content_adapter.remove(content_adapter.getItem(content_adapter.getCount()-1)); - content_adapter.notifyDataSetChanged(); - } - - public void addItem(ProviderItem provider) { - content_adapter.add(provider); - content_adapter.notifyDataSetChanged(); - } - - public void hideAllBut(int position) { - int real_count = content_adapter.getCount(); - for(int i = 0; i < real_count;) - if(i != position) { - content_adapter.hide(i); - position--; - real_count--; - } else { - i++; - } - } - - public void unhideAll() { - if(content_adapter != null) { - content_adapter.unHideAll(); - content_adapter.notifyDataSetChanged(); - } - } - - /** - * @return a new instance of this ListFragment. - */ - public static ProviderListFragment newInstance() { - return new ProviderListFragment(); - } -} diff --git a/app/src/main/java/se/leap/openvpn/CIDRIP.java b/app/src/main/java/se/leap/openvpn/CIDRIP.java deleted file mode 100644 index 8c4b6709..00000000 --- a/app/src/main/java/se/leap/openvpn/CIDRIP.java +++ /dev/null @@ -1,58 +0,0 @@ -package se.leap.openvpn; - -class CIDRIP{ - String mIp; - int len; - public CIDRIP(String ip, String mask){ - mIp=ip; - long netmask=getInt(mask); - - // Add 33. bit to ensure the loop terminates - netmask += 1l << 32; - - int lenZeros = 0; - while((netmask & 0x1) == 0) { - lenZeros++; - netmask = netmask >> 1; - } - // Check if rest of netmask is only 1s - if(netmask != (0x1ffffffffl >> lenZeros)) { - // Asume no CIDR, set /32 - len=32; - } else { - len =32 -lenZeros; - } - - } - @Override - public String toString() { - return String.format("%s/%d",mIp,len); - } - - public boolean normalise(){ - long ip=getInt(mIp); - - long newip = ip & (0xffffffffl << (32 -len)); - if (newip != ip){ - mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24,(newip & 0xff0000) >> 16, (newip & 0xff00) >> 8 ,newip & 0xff); - return true; - } else { - return false; - } - } - static long getInt(String ipaddr) { - String[] ipt = ipaddr.split("\\."); - long ip=0; - - ip += Long.parseLong(ipt[0])<< 24; - ip += Integer.parseInt(ipt[1])<< 16; - ip += Integer.parseInt(ipt[2])<< 8; - ip += Integer.parseInt(ipt[3]); - - return ip; - } - public long getInt() { - return getInt(mIp); - } - -} \ No newline at end of file diff --git a/app/src/main/java/se/leap/openvpn/ConfigParser.java b/app/src/main/java/se/leap/openvpn/ConfigParser.java deleted file mode 100644 index df4eae1b..00000000 --- a/app/src/main/java/se/leap/openvpn/ConfigParser.java +++ /dev/null @@ -1,569 +0,0 @@ -package se.leap.openvpn; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.Reader; -import java.util.HashMap; -import java.util.Locale; -import java.util.Vector; - -//! Openvpn Config FIle Parser, probably not 100% accurate but close enough - -// And rember, this is valid :) -// -- -// bar -// -public class ConfigParser { - - - private HashMap>> options = new HashMap>>(); - public void parseConfig(Reader reader) throws IOException, ConfigParseError { - - - BufferedReader br =new BufferedReader(reader); - - @SuppressWarnings("unused") - int lineno=0; - - while (true){ - String line = br.readLine(); - if(line==null) - break; - lineno++; - Vector args = parseline(line); - if(args.size() ==0) - continue; - - - if(args.get(0).startsWith("--")) - args.set(0, args.get(0).substring(2)); - - checkinlinefile(args,br); - - String optionname = args.get(0); - if(!options.containsKey(optionname)) { - options.put(optionname, new Vector>()); - } - options.get(optionname).add(args); - } - } - public void setDefinition(HashMap>> args) { - options = args; - } - - private void checkinlinefile(Vector args, BufferedReader br) throws IOException, ConfigParseError { - String arg0 = args.get(0); - // CHeck for - if(arg0.startsWith("<") && arg0.endsWith(">")) { - String argname = arg0.substring(1, arg0.length()-1); - String inlinefile = VpnProfile.INLINE_TAG; - - String endtag = String.format("",argname); - do { - String line = br.readLine(); - if(line==null){ - throw new ConfigParseError(String.format("No endtag for starttag <%s> found",argname,argname)); - } - if(line.equals(endtag)) - break; - else { - inlinefile+=line; - inlinefile+= "\n"; - } - } while(true); - - args.clear(); - args.add(argname); - args.add(inlinefile); - } - - } - - enum linestate { - initial, - readin_single_quote - , reading_quoted, reading_unquoted, done} - - private boolean space(char c) { - // I really hope nobody is using zero bytes inside his/her config file - // to sperate parameter but here we go: - return Character.isWhitespace(c) || c == '\0'; - - } - - public class ConfigParseError extends Exception { - private static final long serialVersionUID = -60L; - - public ConfigParseError(String msg) { - super(msg); - } - } - - - // adapted openvpn's parse function to java - private Vector parseline(String line) throws ConfigParseError { - Vector parameters = new Vector(); - - if (line.length()==0) - return parameters; - - - linestate state = linestate.initial; - boolean backslash = false; - char out=0; - - int pos=0; - String currentarg=""; - - do { - // Emulate the c parsing ... - char in; - if(pos < line.length()) - in = line.charAt(pos); - else - in = '\0'; - - if (!backslash && in == '\\' && state != linestate.readin_single_quote) - { - backslash = true; - } - else - { - if (state == linestate.initial) - { - if (!space (in)) - { - if (in == ';' || in == '#') /* comment */ - break; - if (!backslash && in == '\"') - state = linestate.reading_quoted; - else if (!backslash && in == '\'') - state = linestate.readin_single_quote; - else - { - out = in; - state = linestate.reading_unquoted; - } - } - } - else if (state == linestate.reading_unquoted) - { - if (!backslash && space (in)) - state = linestate.done; - else - out = in; - } - else if (state == linestate.reading_quoted) - { - if (!backslash && in == '\"') - state = linestate.done; - else - out = in; - } - else if (state == linestate.readin_single_quote) - { - if (in == '\'') - state = linestate.done; - else - out = in; - } - - if (state == linestate.done) - { - /* ASSERT (parm_len > 0); */ - state = linestate.initial; - parameters.add(currentarg); - currentarg = ""; - out =0; - } - - if (backslash && out!=0) - { - if (!(out == '\\' || out == '\"' || space (out))) - { - throw new ConfigParseError("Options warning: Bad backslash ('\\') usage"); - } - } - backslash = false; - } - - /* store parameter character */ - if (out!=0) - { - currentarg+=out; - } - } while (pos++ < line.length()); - - return parameters; - } - - - final String[] unsupportedOptions = { "config", - "connection", - "proto-force", - "remote-random", - "tls-server" - - }; - - // Ignore all scripts - // in most cases these won't work and user who wish to execute scripts will - // figure out themselves - final String[] ignoreOptions = { "tls-client", - "askpass", - "auth-nocache", - "up", - "down", - "route-up", - "ipchange", - "route-up", - "route-pre-down", - "auth-user-pass-verify", - "dhcp-release", - "dhcp-renew", - "dh", - "management-hold", - "management", - "management-query-passwords", - "pause-exit", - "persist-key", - "register-dns", - "route-delay", - "route-gateway", - "route-metric", - "route-method", - "status", - "script-security", - "show-net-up", - "suppress-timestamps", - "tmp-dir", - "tun-ipv6", - "topology", - "win-sys", - }; - - - // This method is far too long - public VpnProfile convertProfile() throws ConfigParseError{ - boolean noauthtypeset=true; - VpnProfile np = new VpnProfile("converted Profile"); - // Pull, client, tls-client - np.clearDefaults(); - - // XXX we are always client - if(/*options.containsKey("client") || options.containsKey("pull")*/ true) { - np.mUsePull=true; - options.remove("pull"); - options.remove("client"); - } - - Vector secret = getOption("secret", 1, 2); - if(secret!=null) - { - np.mAuthenticationType=VpnProfile.TYPE_STATICKEYS; - noauthtypeset=false; - np.mUseTLSAuth=true; - np.mTLSAuthFilename=secret.get(1); - if(secret.size()==3) - np.mTLSAuthDirection=secret.get(2); - - } - - Vector> routes = getAllOption("route", 1, 4); - if(routes!=null) { - String routeopt = ""; - for(Vector route:routes){ - String netmask = "255.255.255.255"; - if(route.size() >= 3) - netmask = route.get(2); - String net = route.get(1); - try { - CIDRIP cidr = new CIDRIP(net, netmask); - routeopt+=cidr.toString() + " "; - } catch (ArrayIndexOutOfBoundsException aioob) { - throw new ConfigParseError("Could not parse netmask of route " + netmask); - } catch (NumberFormatException ne) { - throw new ConfigParseError("Could not parse netmask of route " + netmask); - } - - } - np.mCustomRoutes=routeopt; - } - - // Also recognize tls-auth [inline] direction ... - Vector> tlsauthoptions = getAllOption("tls-auth", 1, 2); - if(tlsauthoptions!=null) { - for(Vector tlsauth:tlsauthoptions) { - if(tlsauth!=null) - { - if(!tlsauth.get(1).equals("[inline]")) { - np.mTLSAuthFilename=tlsauth.get(1); - np.mUseTLSAuth=true; - } - if(tlsauth.size()==3) - np.mTLSAuthDirection=tlsauth.get(2); - } - } - } - - Vector direction = getOption("key-direction", 1, 1); - if(direction!=null) - np.mTLSAuthDirection=direction.get(1); - - - if(getAllOption("redirect-gateway", 0, 5) != null) - np.mUseDefaultRoute=true; - - Vector dev =getOption("dev",1,1); - Vector devtype =getOption("dev-type",1,1); - - if( (devtype !=null && devtype.get(1).equals("tun")) || - (dev!=null && dev.get(1).startsWith("tun")) || - (devtype ==null && dev == null) ) { - //everything okay - } else { - throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail"); - } - - - - Vector mode =getOption("mode",1,1); - if (mode != null){ - if(!mode.get(1).equals("p2p")) - throw new ConfigParseError("Invalid mode for --mode specified, need p2p"); - } - - Vector port = getOption("port", 1,1); - if(port!=null){ - np.mServerPort = port.get(1); - } - - Vector proto = getOption("proto", 1,1); - if(proto!=null){ - np.mUseUdp=isUdpProto(proto.get(1));; - } - - // Parse remote config - Vector remote = getOption("remote",1,3); - if(remote != null){ - switch (remote.size()) { - case 4: - np.mUseUdp=isUdpProto(remote.get(3)); - case 3: - np.mServerPort = remote.get(2); - case 2: - np.mServerName = remote.get(1); - } - } - - // Parse remote config - Vector location = getOption("location",0,2); - if(location != null && location.size() == 2){ - np.mLocation = location.get(1).replace("__", ", "); - } - - Vector> dhcpoptions = getAllOption("dhcp-option", 2, 2); - if(dhcpoptions!=null) { - for(Vector dhcpoption:dhcpoptions) { - String type=dhcpoption.get(1); - String arg = dhcpoption.get(2); - if(type.equals("DOMAIN")) { - np.mSearchDomain=dhcpoption.get(2); - } else if(type.equals("DNS")) { - np.mOverrideDNS=true; - if(np.mDNS1.equals(VpnProfile.DEFAULT_DNS1)) - np.mDNS1=arg; - else - np.mDNS2=arg; - } - } - } - - Vector ifconfig = getOption("ifconfig", 2, 2); - if(ifconfig!=null) { - CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2)); - np.mIPv4Address=cidr.toString(); - } - - if(getOption("remote-random-hostname", 0, 0)!=null) - np.mUseRandomHostname=true; - - if(getOption("float", 0, 0)!=null) - np.mUseFloat=true; - - if(getOption("comp-lzo", 0, 1)!=null) - np.mUseLzo=true; - - Vector cipher = getOption("cipher", 1, 1); - if(cipher!=null) - np.mCipher= cipher.get(1); - - Vector ca = getOption("ca",1,1); - if(ca!=null){ - np.mCaFilename = ca.get(1); - } - - Vector cert = getOption("cert",1,1); - if(cert!=null){ - np.mClientCertFilename = cert.get(1); - np.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES; - noauthtypeset=false; - } - Vector key= getOption("key",1,1); - if(key!=null) - np.mClientKeyFilename=key.get(1); - - Vector pkcs12 = getOption("pkcs12",1,1); - if(pkcs12!=null) { - np.mPKCS12Filename = pkcs12.get(1); - np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE; - noauthtypeset=false; - } - - Vector tlsremote = getOption("tls-remote",1,1); - if(tlsremote!=null){ - np.mRemoteCN = tlsremote.get(1); - np.mCheckRemoteCN=true; - } - - Vector verb = getOption("verb",1,1); - if(verb!=null){ - np.mVerb=verb.get(1); - } - - - if(getOption("nobind", 0, 0) != null) - np.mNobind=true; - - if(getOption("persist-tun", 0,0) != null) - np.mPersistTun=true; - - Vector connectretry = getOption("connect-retry", 1, 1); - if(connectretry!=null) - np.mConnectRetry =connectretry.get(1); - - Vector connectretrymax = getOption("connect-retry-max", 1, 1); - if(connectretrymax!=null) - np.mConnectRetryMax =connectretrymax.get(1); - - Vector> remotetls = getAllOption("remote-cert-tls", 1, 1); - if(remotetls!=null) - if(remotetls.get(0).get(1).equals("server")) - np.mExpectTLSCert=true; - else - options.put("remotetls",remotetls); - - Vector authuser = getOption("auth-user-pass",0,1); - if(authuser !=null){ - if(noauthtypeset) { - np.mAuthenticationType=VpnProfile.TYPE_USERPASS; - } else if(np.mAuthenticationType==VpnProfile.TYPE_CERTIFICATES) { - np.mAuthenticationType=VpnProfile.TYPE_USERPASS_CERTIFICATES; - } else if(np.mAuthenticationType==VpnProfile.TYPE_KEYSTORE) { - np.mAuthenticationType=VpnProfile.TYPE_USERPASS_KEYSTORE; - } - if(authuser.size()>1) { - // Set option value to password get to get canche to embed later. - np.mUsername=null; - np.mPassword=authuser.get(1); - useEmbbedUserAuth(np,authuser.get(1)); - } - - } - - - // Check the other options - - checkIgnoreAndInvalidOptions(np); - fixup(np); - - return np; - } - - private boolean isUdpProto(String proto) throws ConfigParseError { - boolean isudp; - if(proto.equals("udp") || proto.equals("udp6")) - isudp=true; - else if (proto.equals("tcp-client") || - proto.equals("tcp") || - proto.equals("tcp6") || - proto.endsWith("tcp6-client")) - isudp =false; - else - throw new ConfigParseError("Unsupported option to --proto " + proto); - return isudp; - } - - static public void useEmbbedUserAuth(VpnProfile np,String inlinedata) - { - String data = inlinedata.replace(VpnProfile.INLINE_TAG, ""); - String[] parts = data.split("\n"); - if(parts.length >= 2) { - np.mUsername=parts[0]; - np.mPassword=parts[1]; - } - } - - private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError { - for(String option:unsupportedOptions) - if(options.containsKey(option)) - throw new ConfigParseError(String.format("Unsupported Option %s encountered in config file. Aborting",option)); - - for(String option:ignoreOptions) - // removing an item which is not in the map is no error - options.remove(option); - - if(options.size()> 0) { - String custom = "# These Options were found in the config file do not map to config settings:\n"; - - for(Vector> option:options.values()) { - for(Vector optionsline: option) { - for (String arg : optionsline) - custom+= VpnProfile.openVpnEscape(arg) + " "; - } - custom+="\n"; - - } - np.mCustomConfigOptions = custom; - np.mUseCustomConfig=true; - - } - } - - - private void fixup(VpnProfile np) { - if(np.mRemoteCN.equals(np.mServerName)) { - np.mRemoteCN=""; - } - } - - private Vector getOption(String option, int minarg, int maxarg) throws ConfigParseError { - Vector> alloptions = getAllOption(option, minarg, maxarg); - if(alloptions==null) - return null; - else - return alloptions.lastElement(); - } - - - private Vector> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError { - Vector> args = options.get(option); - if(args==null) - return null; - - for(Vector optionline:args) - - if(optionline.size()< (minarg+1) || optionline.size() > maxarg+1) { - String err = String.format(Locale.getDefault(),"Option %s has %d parameters, expected between %d and %d", - option,optionline.size()-1,minarg,maxarg ); - throw new ConfigParseError(err); - } - options.remove(option); - return args; - } - -} - - - - diff --git a/app/src/main/java/se/leap/openvpn/LICENSE.txt b/app/src/main/java/se/leap/openvpn/LICENSE.txt deleted file mode 100644 index d897edea..00000000 --- a/app/src/main/java/se/leap/openvpn/LICENSE.txt +++ /dev/null @@ -1,24 +0,0 @@ -License for OpenVPN for Android. Please note that the thirdparty libraries/executables may have other license (OpenVPN, lzo, OpenSSL, Google Breakpad) - -Copyright (c) 2012-2013, Arne Schwabe - All rights reserved. - -If you need a non GPLv2 license of the source please contact me. - -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 2 -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, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -In addition, as a special exception, the copyright holders give -permission to link the code of portions of this program with the -OpenSSL library. diff --git a/app/src/main/java/se/leap/openvpn/LaunchVPN.java b/app/src/main/java/se/leap/openvpn/LaunchVPN.java deleted file mode 100644 index 89f2d372..00000000 --- a/app/src/main/java/se/leap/openvpn/LaunchVPN.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package se.leap.openvpn; - -import java.io.IOException; -import java.util.Collection; -import java.util.Vector; - -import se.leap.bitmaskclient.ConfigHelper; -import se.leap.bitmaskclient.EIP; -import se.leap.bitmaskclient.R; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ListActivity; -import android.content.ActivityNotFoundException; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.VpnService; -import android.os.Bundle; -import android.os.Parcelable; -import android.os.ResultReceiver; -import android.preference.PreferenceManager; -import android.text.InputType; -import android.text.method.PasswordTransformationMethod; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.TextView; - -/** - * This Activity actually handles two stages of a launcher shortcut's life cycle. - * - * 1. Your application offers to provide shortcuts to the launcher. When - * the user installs a shortcut, an activity within your application - * generates the actual shortcut and returns it to the launcher, where it - * is shown to the user as an icon. - * - * 2. Any time the user clicks on an installed shortcut, an intent is sent. - * Typically this would then be handled as necessary by an activity within - * your application. - * - * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form - * of an {@link android.content.Intent} that the launcher will use to create the shortcut. - * - * You can also implement this in an interactive way, by having your activity actually present - * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL, - * media item, or action. - * - * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents - * of the incoming {@link android.content.Intent}. - * - * In a real application, you would probably use the shortcut intent to display specific content - * or start a particular operation. - */ -public class LaunchVPN extends ListActivity implements OnItemClickListener { - - public static final String EXTRA_KEY = "se.leap.openvpn.shortcutProfileUUID"; - public static final String EXTRA_NAME = "se.leap.openvpn.shortcutProfileName"; - public static final String EXTRA_HIDELOG = "se.leap.openvpn.showNoLogWindow";; - - public static final int START_VPN_PROFILE= 70; - - // Dashboard, maybe more, want to know! - private ResultReceiver mReceiver; - - private ProfileManager mPM; - private VpnProfile mSelectedProfile; - private boolean mhideLog=false; - - private boolean mCmfixed=false; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - mPM =ProfileManager.getInstance(this); - - } - - @Override - protected void onStart() { - super.onStart(); - // Resolve the intent - - final Intent intent = getIntent(); - final String action = intent.getAction(); - - // If something wants feedback, they sent us a Receiver - mReceiver = intent.getParcelableExtra(EIP.RECEIVER_TAG); - - // If the intent is a request to create a shortcut, we'll do that and exit - - - if(Intent.ACTION_MAIN.equals(action)) { - // we got called to be the starting point, most likely a shortcut - String shortcutUUID = intent.getStringExtra( EXTRA_KEY); - String shortcutName = intent.getStringExtra( EXTRA_NAME); - mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); - - VpnProfile profileToConnect = ProfileManager.get(shortcutUUID); - if(shortcutName != null && profileToConnect ==null) - profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName); - - if(profileToConnect ==null) { - OpenVPN.logError(R.string.shortcut_profile_notfound); - // show Log window to display error - showLogWindow(); - finish(); - return; - } - - mSelectedProfile = profileToConnect; - launchVPN(); - - } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) { - createListView(); - } - } - - private void createListView() { - ListView lv = getListView(); - //lv.setTextFilterEnabled(true); - - Collection vpnlist = mPM.getProfiles(); - - Vector vpnnames=new Vector(); - for (VpnProfile vpnProfile : vpnlist) { - vpnnames.add(vpnProfile.mName); - } - - - - ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,vpnnames); - lv.setAdapter(adapter); - - lv.setOnItemClickListener(this); - } - - /** - * This function creates a shortcut and returns it to the caller. There are actually two - * intents that you will send back. - * - * The first intent serves as a container for the shortcut and is returned to the launcher by - * setResult(). This intent must contain three fields: - * - *

    - *
  • {@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.
  • - *
  • {@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with - * the shortcut.
  • - *
  • {@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a - * bitmap, or {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as - * a drawable resource.
  • - *
- * - * If you use a simple drawable resource, note that you must wrapper it using - * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so - * that the launcher can access resources that are stored in your application's .apk file. If - * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras - * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}. - * - * The shortcut intent can be any intent that you wish the launcher to send, when the user - * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW} - * with an appropriate Uri for your content, but any Intent will work here as long as it - * triggers the desired action within your Activity. - * @param profile - */ - private void setupShortcut(VpnProfile profile) { - // First, set up the shortcut intent. For this example, we simply create an intent that - // will bring us directly back to this activity. A more typical implementation would use a - // data Uri in order to display a more specific result, or a custom action in order to - // launch a specific operation. - - Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); - shortcutIntent.setClass(this, LaunchVPN.class); - shortcutIntent.putExtra(EXTRA_KEY,profile.getUUID().toString()); - - // Then, set up the container intent (the response to the caller) - - Intent intent = new Intent(); - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName()); - Parcelable iconResource = Intent.ShortcutIconResource.fromContext( - this, R.drawable.icon); - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); - - // Now, return the result to the launcher - - setResult(RESULT_OK, intent); - } - - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - String profilename = ((TextView) view).getText().toString(); - - VpnProfile profile = mPM.getProfileByName(profilename); - - // if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) { - setupShortcut(profile); - finish(); - return; - // } - - } - - - - private void askForPW(final int type) { - - final EditText entry = new EditText(this); - entry.setSingleLine(); - entry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - entry.setTransformationMethod(new PasswordTransformationMethod()); - - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle("Need " + getString(type)); - dialog.setMessage("Enter the password for profile " + mSelectedProfile.mName); - dialog.setView(entry); - - dialog.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String pw = entry.getText().toString(); - if(type == R.string.password) { - mSelectedProfile.mTransientPW = pw; - } else { - mSelectedProfile.mTransientPCKS12PW = pw; - } - onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); - - } - - }); - dialog.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - - dialog.create().show(); - - } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if(requestCode==START_VPN_PROFILE) { - if(resultCode == Activity.RESULT_OK) { - int needpw = mSelectedProfile.needUserPWInput(); - if(needpw !=0) { - askForPW(needpw); - } else { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean showlogwindow = prefs.getBoolean("showlogwindow", false); - - if(!mhideLog && showlogwindow) - showLogWindow(); - new startOpenVpnThread().start(); - } - } else if (resultCode == Activity.RESULT_CANCELED) { - // User does not want us to start, so we just vanish (well, now we tell our receiver, then vanish) - Bundle resultData = new Bundle(); - // For now, nothing else is calling, so this "request" string is good enough - resultData.putString(EIP.REQUEST_TAG, EIP.ACTION_START_EIP); - mReceiver.send(RESULT_CANCELED, resultData); - finish(); - } - } - } - void showLogWindow() { - - Intent startLW = new Intent(getBaseContext(),LogWindow.class); - startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - startActivity(startLW); - - } - - void showConfigErrorDialog(int vpnok) { - AlertDialog.Builder d = new AlertDialog.Builder(this); - d.setTitle(R.string.config_error_found); - d.setMessage(vpnok); - d.setPositiveButton(android.R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - - } - }); - d.show(); - } - - void launchVPN () { - int vpnok = mSelectedProfile.checkProfile(this); - if(vpnok!= R.string.no_error_found) { - showConfigErrorDialog(vpnok); - return; - } - - Intent intent = VpnService.prepare(this); - // Check if we want to fix /dev/tun - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean usecm9fix = prefs.getBoolean("useCM9Fix", false); - boolean loadTunModule = prefs.getBoolean("loadTunModule", false); - - if(loadTunModule) - execeuteSUcmd("insmod /system/lib/modules/tun.ko"); - - if(usecm9fix && !mCmfixed ) { - execeuteSUcmd("chown system /dev/tun"); - } - - - if (intent != null) { - // Start the query - try { - startActivityForResult(intent, START_VPN_PROFILE); - } catch (ActivityNotFoundException ane) { - // Shame on you Sony! At least one user reported that - // an official Sony Xperia Arc S image triggers this exception - OpenVPN.logError(R.string.no_vpn_support_image); - showLogWindow(); - } - } else { - onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); - } - - } - - private void execeuteSUcmd(String command) { - ProcessBuilder pb = new ProcessBuilder(new String[] {"su","-c",command}); - try { - Process p = pb.start(); - int ret = p.waitFor(); - if(ret ==0) - mCmfixed=true; - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private class startOpenVpnThread extends Thread { - - @Override - public void run() { - VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); - // Tell whom-it-may-concern that we started VPN - Bundle resultData = new Bundle(); - // For now, nothing else is calling, so this "request" string is good enough - resultData.putString(EIP.REQUEST_TAG, EIP.ACTION_START_EIP); - mReceiver.send(RESULT_OK, resultData); - finish(); - - } - - } - - -} diff --git a/app/src/main/java/se/leap/openvpn/LogWindow.java b/app/src/main/java/se/leap/openvpn/LogWindow.java deleted file mode 100644 index b87c4999..00000000 --- a/app/src/main/java/se/leap/openvpn/LogWindow.java +++ /dev/null @@ -1,340 +0,0 @@ -package se.leap.openvpn; - -import java.util.Vector; - -import se.leap.bitmaskclient.R; - -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.ListActivity; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.database.DataSetObserver; -import android.os.Bundle; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Message; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; -import se.leap.openvpn.OpenVPN.LogItem; -import se.leap.openvpn.OpenVPN.LogListener; -import se.leap.openvpn.OpenVPN.StateListener; - -public class LogWindow extends ListActivity implements StateListener { - private static final int START_VPN_CONFIG = 0; - private String[] mBconfig=null; - - - class LogWindowListAdapter implements ListAdapter, LogListener, Callback { - - private static final int MESSAGE_NEWLOG = 0; - - private static final int MESSAGE_CLEARLOG = 1; - - private Vector myEntries=new Vector(); - - private Handler mHandler; - - private Vector observers=new Vector(); - - - public LogWindowListAdapter() { - initLogBuffer(); - - if (mHandler == null) { - mHandler = new Handler(this); - } - - OpenVPN.addLogListener(this); - } - - - - private void initLogBuffer() { - myEntries.clear(); - for (LogItem litem : OpenVPN.getlogbuffer()) { - myEntries.add(litem.getString(getContext())); - } - } - - String getLogStr() { - String str = ""; - for(String entry:myEntries) { - str+=entry + '\n'; - } - return str; - } - - - private void shareLog() { - Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_TEXT, getLogStr()); - shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.bitmask_openvpn_log_file)); - shareIntent.setType("text/plain"); - startActivity(Intent.createChooser(shareIntent, "Send Logfile")); - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - observers.add(observer); - - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - observers.remove(observer); - } - - @Override - public int getCount() { - return myEntries.size(); - } - - @Override - public Object getItem(int position) { - return myEntries.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - TextView v; - if(convertView==null) - v = new TextView(getBaseContext()); - else - v = (TextView) convertView; - v.setText(myEntries.get(position)); - return v; - } - - @Override - public int getItemViewType(int position) { - return 0; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean isEmpty() { - return myEntries.isEmpty(); - - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int position) { - return true; - } - - @Override - public void newLog(LogItem logmessage) { - Message msg = Message.obtain(); - msg.what=MESSAGE_NEWLOG; - Bundle mbundle=new Bundle(); - mbundle.putString("logmessage", logmessage.getString(getBaseContext())); - msg.setData(mbundle); - mHandler.sendMessage(msg); - } - - @Override - public boolean handleMessage(Message msg) { - // We have been called - if(msg.what==MESSAGE_NEWLOG) { - - String logmessage = msg.getData().getString("logmessage"); - myEntries.add(logmessage); - - for (DataSetObserver observer : observers) { - observer.onChanged(); - } - } else if (msg.what == MESSAGE_CLEARLOG) { - initLogBuffer(); - for (DataSetObserver observer : observers) { - observer.onInvalidated(); - } - } - - return true; - } - - void clearLog() { - // Actually is probably called from GUI Thread as result of the user - // pressing a button. But better safe than sorry - OpenVPN.clearLog(); - OpenVPN.logMessage(0,"","Log cleared."); - mHandler.sendEmptyMessage(MESSAGE_CLEARLOG); - } - } - - - - private LogWindowListAdapter ladapter; - private TextView mSpeedView; - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(item.getItemId()==R.id.clearlog) { - ladapter.clearLog(); - return true; - } else if(item.getItemId()==R.id.cancel){ - Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.title_cancel); - builder.setMessage(R.string.cancel_connection_query); - builder.setNegativeButton(android.R.string.no, null); - builder.setPositiveButton(android.R.string.yes, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - ProfileManager.setConntectedVpnProfileDisconnected(getApplicationContext()); - OpenVpnManagementThread.stopOpenVPN(); - } - }); - - builder.show(); - return true; - } else if(item.getItemId()==R.id.info) { - if(mBconfig==null) - OpenVPN.triggerLogBuilderConfig(); - - } else if(item.getItemId()==R.id.send) { - ladapter.shareLog(); - } - - return super.onOptionsItemSelected(item); - - } - - protected Context getContext() { - return this; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.logmenu, menu); - return true; - } - - - @Override - protected void onResume() { - super.onResume(); - OpenVPN.addStateListener(this); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == START_VPN_CONFIG && resultCode==RESULT_OK) { - String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); - - final VpnProfile profile = ProfileManager.get(configuredVPN); - ProfileManager.getInstance(this).saveProfile(this, profile); - // Name could be modified, reset List adapter - - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle(R.string.configuration_changed); - dialog.setMessage(R.string.restart_vpn_after_change); - - - dialog.setPositiveButton(R.string.restart, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(getBaseContext(), LaunchVPN.class); - intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUIDString()); - intent.setAction(Intent.ACTION_MAIN); - startActivity(intent); - } - - - }); - dialog.setNegativeButton(R.string.ignore, null); - dialog.create().show(); - } - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - protected void onStop() { - super.onStop(); - OpenVPN.removeStateListener(this); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.logwindow); - ListView lv = getListView(); - - lv.setOnItemLongClickListener(new OnItemLongClickListener() { - - @Override - public boolean onItemLongClick(AdapterView parent, View view, - int position, long id) { - ClipboardManager clipboard = (ClipboardManager) - getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Log Entry",((TextView) view).getText()); - clipboard.setPrimaryClip(clip); - Toast.makeText(getBaseContext(), R.string.copied_entry, Toast.LENGTH_SHORT).show(); - return true; - } - }); - - ladapter = new LogWindowListAdapter(); - lv.setAdapter(ladapter); - - mSpeedView = (TextView) findViewById(R.id.speed); - } - - @Override - public void updateState(final String status,final String logmessage, final int resid) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - String prefix=getString(resid) + ":"; - if (status.equals("BYTECOUNT") || status.equals("NOPROCESS") ) - prefix=""; - mSpeedView.setText(prefix + logmessage); - } - }); - - } - - @Override - protected void onDestroy() { - super.onDestroy(); - OpenVPN.removeLogListener(ladapter); - } - -} diff --git a/app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java b/app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java deleted file mode 100644 index 777402b4..00000000 --- a/app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java +++ /dev/null @@ -1,86 +0,0 @@ -package se.leap.openvpn; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.NetworkInfo.State; -import android.preference.PreferenceManager; -import se.leap.bitmaskclient.R; - -public class NetworkSateReceiver extends BroadcastReceiver { - private int lastNetwork=-1; - private OpenVpnManagementThread mManangement; - - private String lastStateMsg=null; - - public NetworkSateReceiver(OpenVpnManagementThread managementThread) { - super(); - mManangement = managementThread; - } - - @Override - public void onReceive(Context context, Intent intent) { - NetworkInfo networkInfo = getCurrentNetworkInfo(context); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean sendusr1 = prefs.getBoolean("netchangereconnect", true); - - String netstatestring; - if(networkInfo==null) - netstatestring = "not connected"; - else { - String subtype = networkInfo.getSubtypeName(); - if(subtype==null) - subtype = ""; - String extrainfo = networkInfo.getExtraInfo(); - if(extrainfo==null) - extrainfo=""; - - /* - if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) { - WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - WifiInfo wifiinfo = wifiMgr.getConnectionInfo(); - extrainfo+=wifiinfo.getBSSID(); - - subtype += wifiinfo.getNetworkId(); - }*/ - - - - netstatestring = String.format("%2$s %4$s to %1$s %3$s",networkInfo.getTypeName(), - networkInfo.getDetailedState(),extrainfo,subtype ); - } - - - - if(networkInfo!=null && networkInfo.getState() == State.CONNECTED) { - int newnet = networkInfo.getType(); - - if(sendusr1 && lastNetwork!=newnet) - mManangement.reconnect(); - - lastNetwork = newnet; - } else if (networkInfo==null) { - // Not connected, stop openvpn, set last connected network to no network - lastNetwork=-1; - if(sendusr1) - mManangement.signalusr1(); - } - - if(!netstatestring.equals(lastStateMsg)) - OpenVPN.logInfo(R.string.netstatus, netstatestring); - lastStateMsg=netstatestring; - - } - - private NetworkInfo getCurrentNetworkInfo(Context context) { - ConnectivityManager conn = (ConnectivityManager) - context.getSystemService(Context.CONNECTIVITY_SERVICE); - - NetworkInfo networkInfo = conn.getActiveNetworkInfo(); - return networkInfo; - } - -} diff --git a/app/src/main/java/se/leap/openvpn/OpenVPN.java b/app/src/main/java/se/leap/openvpn/OpenVPN.java deleted file mode 100644 index 8acdc423..00000000 --- a/app/src/main/java/se/leap/openvpn/OpenVPN.java +++ /dev/null @@ -1,250 +0,0 @@ -package se.leap.openvpn; - -import java.util.LinkedList; -import java.util.Locale; -import java.util.Vector; - -import se.leap.bitmaskclient.R; - - -import android.content.Context; -import android.os.Build; -import android.util.Log; - -public class OpenVPN { - - - public static LinkedList logbuffer; - - private static Vector logListener; - private static Vector stateListener; - private static String[] mBconfig; - - private static String mLaststatemsg; - - private static String mLaststate; - - private static int mLastStateresid=R.string.state_noprocess; - public static String TAG="se.leap.openvpn.OpenVPN"; - - static { - logbuffer = new LinkedList(); - logListener = new Vector(); - stateListener = new Vector(); - logInformation(); - } - - public static class LogItem { - public static final int ERROR = 1; - public static final int INFO = 2; - public static final int VERBOSE = 3; - - private Object [] mArgs = null; - private String mMessage = null; - private int mRessourceId; - // Default log priority - int mLevel = INFO; - - public LogItem(int ressourceId, Object[] args) { - mRessourceId = ressourceId; - mArgs = args; - } - - - public LogItem(int loglevel,int ressourceId, Object[] args) { - mRessourceId = ressourceId; - mArgs = args; - mLevel = loglevel; - } - - - public LogItem(String message) { - - mMessage = message; - } - - public LogItem(int loglevel, String msg) { - mLevel = loglevel; - mMessage = msg; - } - - - public LogItem(int loglevel, int ressourceId) { - mRessourceId =ressourceId; - mLevel = loglevel; - } - - - public String getString(Context c) { - if(mMessage !=null) { - return mMessage; - } else { - if(c!=null) { - if(mArgs == null) - return c.getString(mRessourceId); - else - return c.getString(mRessourceId,mArgs); - } else { - String str = String.format(Locale.ENGLISH,"Log (no context) resid %d", mRessourceId); - if(mArgs !=null) - for(Object o:mArgs) - str += "|" + o.toString(); - return str; - } - } - } - } - - private static final int MAXLOGENTRIES = 500; - - - public static final String MANAGMENT_PREFIX = "M:"; - - - - - - - public interface LogListener { - void newLog(LogItem logItem); - } - - public interface StateListener { - void updateState(String state, String logmessage, int localizedResId); - } - - synchronized static void logMessage(int level,String prefix, String message) - { - newlogItem(new LogItem(prefix + message)); - Log.d("OpenVPN log item", message); - } - - synchronized static void clearLog() { - logbuffer.clear(); - logInformation(); - } - - private static void logInformation() { - - logInfo(R.string.mobile_info,Build.MODEL, Build.BOARD,Build.BRAND,Build.VERSION.SDK_INT); - } - - public synchronized static void addLogListener(LogListener ll){ - logListener.add(ll); - } - - public synchronized static void removeLogListener(LogListener ll) { - logListener.remove(ll); - } - - - public synchronized static void addStateListener(StateListener sl){ - stateListener.add(sl); - if(mLaststate!=null) - sl.updateState(mLaststate, mLaststatemsg, mLastStateresid); - } - - private static int getLocalizedState(String state){ - if (state.equals("CONNECTING")) - return R.string.state_connecting; - else if (state.equals("WAIT")) - return R.string.state_wait; - else if (state.equals("AUTH")) - return R.string.state_auth; - else if (state.equals("GET_CONFIG")) - return R.string.state_get_config; - else if (state.equals("ASSIGN_IP")) - return R.string.state_assign_ip; - else if (state.equals("ADD_ROUTES")) - return R.string.state_add_routes; - else if (state.equals("CONNECTED")) - return R.string.state_connected; - else if (state.equals("RECONNECTING")) - return R.string.state_reconnecting; - else if (state.equals("EXITING")) - return R.string.state_exiting; - else if (state.equals("RESOLVE")) - return R.string.state_resolve; - else if (state.equals("TCP_CONNECT")) - return R.string.state_tcp_connect; - else if (state.equals("FATAL")) - return R.string.eip_state_not_connected; - else - return R.string.unknown_state; - - } - - public synchronized static void removeStateListener(StateListener sl) { - stateListener.remove(sl); - } - - - synchronized public static LogItem[] getlogbuffer() { - - // The stoned way of java to return an array from a vector - // brought to you by eclipse auto complete - return (LogItem[]) logbuffer.toArray(new LogItem[logbuffer.size()]); - - } - public static void logBuilderConfig(String[] bconfig) { - mBconfig = bconfig; - } - public static void triggerLogBuilderConfig() { - if(mBconfig==null) { - logMessage(0, "", "No active interface"); - } else { - for (String item : mBconfig) { - logMessage(0, "", item); - } - } - - } - - public static void updateStateString (String state, String msg) { - int rid = getLocalizedState(state); - updateStateString(state, msg,rid); - } - - public synchronized static void updateStateString(String state, String msg, int resid) { - if (! "BYTECOUNT".equals(state)) { - mLaststate= state; - mLaststatemsg = msg; - mLastStateresid = resid; - - for (StateListener sl : stateListener) { - sl.updateState(state,msg,resid); - } - } - } - - public static void logInfo(String message) { - newlogItem(new LogItem(LogItem.INFO, message)); - } - - public static void logInfo(int ressourceId, Object... args) { - newlogItem(new LogItem(LogItem.INFO, ressourceId, args)); - } - - private static void newlogItem(LogItem logItem) { - logbuffer.addLast(logItem); - if(logbuffer.size()>MAXLOGENTRIES) - logbuffer.removeFirst(); - - for (LogListener ll : logListener) { - ll.newLog(logItem); - } - } - - public static void logError(String msg) { - newlogItem(new LogItem(LogItem.ERROR, msg)); - - } - - public static void logError(int ressourceId) { - newlogItem(new LogItem(LogItem.ERROR, ressourceId)); - } - public static void logError(int ressourceId, Object... args) { - newlogItem(new LogItem(LogItem.ERROR, ressourceId,args)); - } - -} diff --git a/app/src/main/java/se/leap/openvpn/OpenVPNThread.java b/app/src/main/java/se/leap/openvpn/OpenVPNThread.java deleted file mode 100644 index ffd21732..00000000 --- a/app/src/main/java/se/leap/openvpn/OpenVPNThread.java +++ /dev/null @@ -1,130 +0,0 @@ -package se.leap.openvpn; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.LinkedList; - -import se.leap.bitmaskclient.R; - -import android.util.Log; -import se.leap.openvpn.OpenVPN.LogItem; - -public class OpenVPNThread implements Runnable { - private static final String DUMP_PATH_STRING = "Dump path: "; - private static final String TAG = "OpenVPN"; - private String[] mArgv; - private Process mProcess; - private String mNativeDir; - private OpenVpnService mService; - private String mDumpPath; - - public OpenVPNThread(OpenVpnService service,String[] argv, String nativelibdir) - { - mArgv = argv; - mNativeDir = nativelibdir; - mService = service; - } - - public void stopProcess() { - mProcess.destroy(); - } - - - - @Override - public void run() { - try { - Log.i(TAG, "Starting openvpn"); - startOpenVPNThreadArgs(mArgv); - Log.i(TAG, "Giving up"); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "OpenVPNThread Got " + e.toString()); - } finally { - int exitvalue = 0; - try { - exitvalue = mProcess.waitFor(); - } catch ( IllegalThreadStateException ite) { - OpenVPN.logError("Illegal Thread state: " + ite.getLocalizedMessage()); - } catch (InterruptedException ie) { - OpenVPN.logError("InterruptedException: " + ie.getLocalizedMessage()); - } - if( exitvalue != 0) - OpenVPN.logError("Process exited with exit value " + exitvalue); - -// OpenVPN.updateStateString("NOPROCESS","No process running.", R.string.state_noprocess); fixes bug #4565 - if(mDumpPath!=null) { - try { - BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log")); - for(LogItem li :OpenVPN.getlogbuffer()){ - logout.write(li.getString(null) + "\n"); - } - logout.close(); - OpenVPN.logError(R.string.minidump_generated); - } catch (IOException e) { - OpenVPN.logError("Writing minidump log: " +e.getLocalizedMessage()); - } - } - - mService.processDied(); - Log.i(TAG, "Exiting"); - } - } - - private void startOpenVPNThreadArgs(String[] argv) { - LinkedList argvlist = new LinkedList(); - - for(String arg:argv) - argvlist.add(arg); - - ProcessBuilder pb = new ProcessBuilder(argvlist); - // Hack O rama - - // Hack until I find a good way to get the real library path - String applibpath = argv[0].replace("/cache/" + VpnProfile.MINIVPN , "/lib"); - - String lbpath = pb.environment().get("LD_LIBRARY_PATH"); - if(lbpath==null) - lbpath = applibpath; - else - lbpath = lbpath + ":" + applibpath; - - if (!applibpath.equals(mNativeDir)) { - lbpath = lbpath + ":" + mNativeDir; - } - - pb.environment().put("LD_LIBRARY_PATH", lbpath); - pb.redirectErrorStream(true); - try { - mProcess = pb.start(); - // Close the output, since we don't need it - mProcess.getOutputStream().close(); - InputStream in = mProcess.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(in)); - - while(true) { - String logline = br.readLine(); - if(logline==null) - return; - - if (logline.startsWith(DUMP_PATH_STRING)) - mDumpPath = logline.substring(DUMP_PATH_STRING.length()); - - - OpenVPN.logMessage(0, "P:", logline); - } - - - } catch (IOException e) { - OpenVPN.logMessage(0, "", "Error reading from output of OpenVPN process"+ e.getLocalizedMessage()); - e.printStackTrace(); - stopProcess(); - } - - - } -} diff --git a/app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java b/app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java deleted file mode 100644 index 27a3db65..00000000 --- a/app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java +++ /dev/null @@ -1,592 +0,0 @@ -package se.leap.openvpn; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.util.LinkedList; -import java.util.Vector; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; - -import se.leap.bitmaskclient.R; -import android.content.SharedPreferences; -import android.net.LocalServerSocket; -import android.net.LocalSocket; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.preference.PreferenceManager; -import android.util.Base64; -import android.util.Log; - -public class OpenVpnManagementThread implements Runnable { - - private static final String TAG = "openvpn"; - private LocalSocket mSocket; - private VpnProfile mProfile; - private OpenVpnService mOpenVPNService; - private LinkedList mFDList=new LinkedList(); - private int mBytecountinterval=2; - private long mLastIn=0; - private long mLastOut=0; - private LocalServerSocket mServerSocket; - private boolean mReleaseHold=true; - private boolean mWaitingForRelease=false; - private long mLastHoldRelease=0; - - private static Vector active=new Vector(); - - static private native void jniclose(int fdint); - static private native byte[] rsasign(byte[] input,int pkey) throws InvalidKeyException; - - public OpenVpnManagementThread(VpnProfile profile, LocalServerSocket mgmtsocket, OpenVpnService openVpnService) { - mProfile = profile; - mServerSocket = mgmtsocket; - mOpenVPNService = openVpnService; - - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(openVpnService); - boolean managemeNetworkState = prefs.getBoolean("netchangereconnect", true); - if(managemeNetworkState) - mReleaseHold=false; - - } - - static { - System.loadLibrary("opvpnutil"); - } - - public void managmentCommand(String cmd) { - if(mSocket!=null) { - try { - mSocket.getOutputStream().write(cmd.getBytes()); - mSocket.getOutputStream().flush(); - } catch (IOException e) { - // Ignore socket stack traces - } - } - } - - - @Override - public void run() { - Log.i(TAG, "Managment Socket Thread started"); - byte [] buffer =new byte[2048]; - // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad - - String pendingInput=""; - active.add(this); - - try { - // Wait for a client to connect - mSocket= mServerSocket.accept(); - InputStream instream = mSocket.getInputStream(); - - while(true) { - int numbytesread = instream.read(buffer); - if(numbytesread==-1) - return; - - FileDescriptor[] fds = null; - try { - fds = mSocket.getAncillaryFileDescriptors(); - } catch (IOException e) { - OpenVPN.logMessage(0, "", "Error reading fds from socket" + e.getLocalizedMessage()); - e.printStackTrace(); - } - if(fds!=null){ - - for (FileDescriptor fd : fds) { - - mFDList.add(fd); - } - } - - String input = new String(buffer,0,numbytesread,"UTF-8"); - - pendingInput += input; - - pendingInput=processInput(pendingInput); - - - - } - } catch (IOException e) { - e.printStackTrace(); - } - active.remove(this); - } - - //! Hack O Rama 2000! - private void protectFileDescriptor(FileDescriptor fd) { - Exception exp=null; - try { - Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$"); - int fdint = (Integer) getInt.invoke(fd); - - // You can even get more evil by parsing toString() and extract the int from that :) - - mOpenVPNService.protect(fdint); - - //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint); - //pfd.close(); - jniclose(fdint); - return; - } catch (NoSuchMethodException e) { - exp =e; - } catch (IllegalArgumentException e) { - exp =e; - } catch (IllegalAccessException e) { - exp =e; - } catch (InvocationTargetException e) { - exp =e; - } catch (NullPointerException e) { - exp =e; - } - if(exp!=null) { - exp.printStackTrace(); - Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd); - OpenVPN.logMessage(0, "", "Failed to retrieve fd from socket: " + exp.getLocalizedMessage()); - } - } - - private String processInput(String pendingInput) { - - - while(pendingInput.contains("\n")) { - String[] tokens = pendingInput.split("\\r?\\n", 2); - processCommand(tokens[0]); - if(tokens.length == 1) - // No second part, newline was at the end - pendingInput=""; - else - pendingInput=tokens[1]; - } - return pendingInput; - } - - - private void processCommand(String command) { - Log.d(TAG, "processCommand: " + command); - - if (command.startsWith(">") && command.contains(":")) { - String[] parts = command.split(":",2); - String cmd = parts[0].substring(1); - String argument = parts[1]; - if(cmd.equals("INFO")) { - // Ignore greeting from mgmt - //logStatusMessage(command); - }else if (cmd.equals("PASSWORD")) { - processPWCommand(argument); - } else if (cmd.equals("HOLD")) { - handleHold(); - } else if (cmd.equals("NEED-OK")) { - processNeedCommand(argument); - } else if (cmd.equals("BYTECOUNT")){ - processByteCount(argument); - } else if (cmd.equals("STATE")) { - processState(argument); - } else if (cmd.equals("FATAL")){ - processState(","+cmd+","); //handles FATAL as state - } else if (cmd.equals("PROXY")) { - processProxyCMD(argument); - } else if (cmd.equals("LOG")) { - String[] args = argument.split(",",3); - // 0 unix time stamp - // 1 log level N,I,E etc. - // 2 log message - OpenVPN.logMessage(0, "", args[2]); - } else if (cmd.equals("RSA_SIGN")) { - processSignCommand(argument); - } else { - OpenVPN.logMessage(0, "MGMT:", "Got unrecognized command" + command); - Log.i(TAG, "Got unrecognized command" + command); - } - } else if (command.startsWith("SUCCESS:")) { //Fixes bug LEAP #4565 - if (command.equals("SUCCESS: signal SIGINT thrown")){ - Log.d(TAG, "SUCCESS: signal SIGINT thrown"); - processState(",EXITING,SIGINT,,"); - } - } else { - Log.i(TAG, "Got unrecognized line from managment" + command); - OpenVPN.logMessage(0, "MGMT:", "Got unrecognized line from management:" + command); - } - } - private void handleHold() { - if(mReleaseHold) { - releaseHoldCmd(); - } else { - mWaitingForRelease=true; - OpenVPN.updateStateString("NONETWORK", "",R.string.state_nonetwork); - } - } - private void releaseHoldCmd() { - if ((System.currentTimeMillis()- mLastHoldRelease) < 5000) { - try { - Thread.sleep(3000); - } catch (InterruptedException e) {} - - } - mWaitingForRelease=false; - mLastHoldRelease = System.currentTimeMillis(); - managmentCommand("hold release\n"); - managmentCommand("bytecount " + mBytecountinterval + "\n"); - managmentCommand("state on\n"); - } - - public void releaseHold() { - mReleaseHold=true; - if(mWaitingForRelease) - releaseHoldCmd(); - - } - - private void processProxyCMD(String argument) { - String[] args = argument.split(",",3); - SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); - - - if(args.length >= 2) { - String proto = args[1]; - if(proto.equals("UDP")) { - proxyaddr=null; - } - } - - if(proxyaddr instanceof InetSocketAddress ){ - InetSocketAddress isa = (InetSocketAddress) proxyaddr; - - OpenVPN.logInfo(R.string.using_proxy, isa.getHostName(),isa.getPort()); - - String proxycmd = String.format("proxy HTTP %s %d\n", isa.getHostName(),isa.getPort()); - managmentCommand(proxycmd); - } else { - managmentCommand("proxy NONE\n"); - } - - } - private void processState(String argument) { - String[] args = argument.split(",",3); - String currentstate = args[1]; - if(args[2].equals(",,")){ - OpenVPN.updateStateString(currentstate,""); - } - else if (args[2].endsWith(",,")){ //fixes LEAP Bug #4546 - args[2] = (String) args[2].subSequence(0, args[2].length()-2); - Log.d(TAG, "processState() STATE: "+ currentstate + " msg: " + args[2]); - OpenVPN.updateStateString(currentstate,args[2]); - } - else{ - OpenVPN.updateStateString(currentstate,args[2]); - } - } - - private static int repeated_byte_counts = 0; - private void processByteCount(String argument) { - // >BYTECOUNT:{BYTES_IN},{BYTES_OUT} - int comma = argument.indexOf(','); - long in = Long.parseLong(argument.substring(0, comma)); - long out = Long.parseLong(argument.substring(comma+1)); - - long diffin = in - mLastIn; - long diffout = out - mLastOut; - if(diffin == 0 && diffout == 0) - repeated_byte_counts++; - if(repeated_byte_counts > 3) - Log.d("OpenVPN log", "Repeated byte count = " + repeated_byte_counts); - mLastIn=in; - mLastOut=out; - - String netstat = String.format("In: %8s, %8s/s Out %8s, %8s/s", - humanReadableByteCount(in, false), - humanReadableByteCount(diffin, false), - humanReadableByteCount(out, false), - humanReadableByteCount(diffout, false)); - OpenVPN.updateStateString("BYTECOUNT",netstat); - - - } - - // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java - public static String humanReadableByteCount(long bytes, boolean si) { - int unit = si ? 1000 : 1024; - if (bytes < unit) return bytes + " B"; - int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); - return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); - } - - private void processNeedCommand(String argument) { - int p1 =argument.indexOf('\''); - int p2 = argument.indexOf('\'',p1+1); - - String needed = argument.substring(p1+1, p2); - String extra = argument.split(":",2)[1]; - - String status = "ok"; - - - if (needed.equals("PROTECTFD")) { - FileDescriptor fdtoprotect = mFDList.pollFirst(); - protectFileDescriptor(fdtoprotect); - } else if (needed.equals("DNSSERVER")) { - mOpenVPNService.addDNS(extra); - }else if (needed.equals("DNSDOMAIN")){ - mOpenVPNService.setDomain(extra); - } else if (needed.equals("ROUTE")) { - String[] routeparts = extra.split(" "); - mOpenVPNService.addRoute(routeparts[0], routeparts[1]); - } else if (needed.equals("ROUTE6")) { - mOpenVPNService.addRoutev6(extra); - } else if (needed.equals("IFCONFIG")) { - String[] ifconfigparts = extra.split(" "); - int mtu = Integer.parseInt(ifconfigparts[2]); - mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1],mtu,ifconfigparts[3]); - } else if (needed.equals("IFCONFIG6")) { - mOpenVPNService.setLocalIPv6(extra); - - } else if (needed.equals("OPENTUN")) { - if(sendTunFD(needed,extra)) - return; - else - status="cancel"; - // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :( - - } else { - Log.e(TAG,"Unkown needok command " + argument); - return; - } - - String cmd = String.format("needok '%s' %s\n", needed, status); - managmentCommand(cmd); - } - - private boolean sendTunFD (String needed, String extra) { - Exception exp = null; - if(!extra.equals("tun")) { - // We only support tun - String errmsg = String.format("Devicetype %s requested, but only tun is possible with the Android API, sorry!",extra); - OpenVPN.logMessage(0, "", errmsg ); - - return false; - } - ParcelFileDescriptor pfd = mOpenVPNService.openTun(); - if(pfd==null) - return false; - - Method setInt; - int fdint = pfd.getFd(); - try { - setInt = FileDescriptor.class.getDeclaredMethod("setInt$",int.class); - FileDescriptor fdtosend = new FileDescriptor(); - - setInt.invoke(fdtosend,fdint); - - FileDescriptor[] fds = {fdtosend}; - mSocket.setFileDescriptorsForSend(fds); - - Log.d("Openvpn", "Sending FD tosocket: " + fdtosend + " " + fdint + " " + pfd); - // Trigger a send so we can close the fd on our side of the channel - // The API documentation fails to mention that it will not reset the file descriptor to - // be send and will happily send the file descriptor on every write ... - String cmd = String.format("needok '%s' %s\n", needed, "ok"); - managmentCommand(cmd); - - // Set the FileDescriptor to null to stop this mad behavior - mSocket.setFileDescriptorsForSend(null); - - pfd.close(); - - return true; - } catch (NoSuchMethodException e) { - exp =e; - } catch (IllegalArgumentException e) { - exp =e; - } catch (IllegalAccessException e) { - exp =e; - } catch (InvocationTargetException e) { - exp =e; - } catch (IOException e) { - exp =e; - } - if(exp!=null) { - OpenVPN.logMessage(0,"", "Could not send fd over socket:" + exp.getLocalizedMessage()); - exp.printStackTrace(); - } - return false; - } - - private void processPWCommand(String argument) { - //argument has the form Need 'Private Key' password - // or ">PASSWORD:Verification Failed: '%s' ['%s']" - String needed; - - - - try{ - - int p1 = argument.indexOf('\''); - int p2 = argument.indexOf('\'',p1+1); - needed = argument.substring(p1+1, p2); - if (argument.startsWith("Verification Failed")) { - proccessPWFailed(needed, argument.substring(p2+1)); - return; - } - } catch (StringIndexOutOfBoundsException sioob) { - OpenVPN.logMessage(0, "", "Could not parse management Password command: " + argument); - return; - } - - String pw=null; - - if(needed.equals("Private Key")) { - pw = mProfile.getPasswordPrivateKey(); - } else if (needed.equals("Auth")) { - String usercmd = String.format("username '%s' %s\n", - needed, VpnProfile.openVpnEscape(mProfile.mUsername)); - managmentCommand(usercmd); - pw = mProfile.getPasswordAuth(); - } - if(pw!=null) { - String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); - managmentCommand(cmd); - } else { - OpenVPN.logMessage(0, OpenVPN.MANAGMENT_PREFIX, String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed)); - } - - } - - - - - private void proccessPWFailed(String needed, String args) { - OpenVPN.updateStateString("AUTH_FAILED", needed + args,R.string.state_auth_failed); - } - private void logStatusMessage(String command) { - OpenVPN.logMessage(0,"MGMT:", command); - } - - - public static boolean stopOpenVPN() { - boolean sendCMD=false; - for (OpenVpnManagementThread mt: active){ - mt.managmentCommand("signal SIGINT\n"); - sendCMD=true; - try { - if(mt.mSocket !=null) - mt.mSocket.close(); - } catch (IOException e) { - // Ignore close error on already closed socket - } - } - return sendCMD; - } - - public void signalusr1() { - mReleaseHold=false; - if(!mWaitingForRelease) - managmentCommand("signal SIGUSR1\n"); - } - - public void reconnect() { - signalusr1(); - releaseHold(); - } - - private void processSignCommand(String b64data) { - - PrivateKey privkey = mProfile.getKeystoreKey(); - Exception err =null; - - byte[] data = Base64.decode(b64data, Base64.DEFAULT); - - // The Jelly Bean *evil* Hack - // 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider - if(Build.VERSION.SDK_INT==16){ - processSignJellyBeans(privkey,data); - return; - } - - - try{ - - - Cipher rsasinger = Cipher.getInstance("RSA/ECB/PKCS1PADDING"); - - rsasinger.init(Cipher.ENCRYPT_MODE, privkey); - - byte[] signed_bytes = rsasinger.doFinal(data); - String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP); - managmentCommand("rsa-sig\n"); - managmentCommand(signed_string); - managmentCommand("\nEND\n"); - } catch (NoSuchAlgorithmException e){ - err =e; - } catch (InvalidKeyException e) { - err =e; - } catch (NoSuchPaddingException e) { - err =e; - } catch (IllegalBlockSizeException e) { - err =e; - } catch (BadPaddingException e) { - err =e; - } - if(err !=null) { - OpenVPN.logError(R.string.error_rsa_sign,err.getClass().toString(),err.getLocalizedMessage()); - } - - } - - - private void processSignJellyBeans(PrivateKey privkey, byte[] data) { - Exception err =null; - try { - Method[] allm = privkey.getClass().getSuperclass().getDeclaredMethods(); - System.out.println(allm); - Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey"); - getKey.setAccessible(true); - - // Real object type is OpenSSLKey - Object opensslkey = getKey.invoke(privkey); - - getKey.setAccessible(false); - - Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext"); - - // integer pointer to EVP_pkey - getPkeyContext.setAccessible(true); - int pkey = (Integer) getPkeyContext.invoke(opensslkey); - getPkeyContext.setAccessible(false); - - byte[] signed_bytes = rsasign(data, pkey); - String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP); - managmentCommand("rsa-sig\n"); - managmentCommand(signed_string); - managmentCommand("\nEND\n"); - - } catch (NoSuchMethodException e) { - err=e; - } catch (IllegalArgumentException e) { - err=e; - } catch (IllegalAccessException e) { - err=e; - } catch (InvocationTargetException e) { - err=e; - } catch (InvalidKeyException e) { - err=e; - } - if(err !=null) { - OpenVPN.logError(R.string.error_rsa_sign,err.getClass().toString(),err.getLocalizedMessage()); - } - - } -} diff --git a/app/src/main/java/se/leap/openvpn/OpenVpnService.java b/app/src/main/java/se/leap/openvpn/OpenVpnService.java deleted file mode 100644 index b5c9c798..00000000 --- a/app/src/main/java/se/leap/openvpn/OpenVpnService.java +++ /dev/null @@ -1,504 +0,0 @@ -package se.leap.openvpn; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Vector; - -import se.leap.bitmaskclient.Dashboard; -import se.leap.bitmaskclient.R; -import android.annotation.TargetApi; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.LocalServerSocket; -import android.net.LocalSocket; -import android.net.LocalSocketAddress; -import android.net.VpnService; -import android.os.Binder; -import android.os.Handler.Callback; -import android.os.Build; -import android.os.IBinder; -import android.os.Message; -import android.os.ParcelFileDescriptor; -import se.leap.openvpn.OpenVPN.StateListener; - -public class OpenVpnService extends VpnService implements StateListener, Callback { - public static final String START_SERVICE = "se.leap.openvpn.START_SERVICE"; - public static final String RETRIEVE_SERVICE = "se.leap.openvpn.RETRIEVE_SERVICE"; - - private Thread mProcessThread=null; - - private Vector mDnslist=new Vector(); - - private VpnProfile mProfile; - - private String mDomain=null; - - private Vector mRoutes=new Vector(); - private Vector mRoutesv6=new Vector(); - - private CIDRIP mLocalIP=null; - - private OpenVpnManagementThread mSocketManager; - - private Thread mSocketManagerThread; - private int mMtu; - private String mLocalIPv6=null; - private NetworkSateReceiver mNetworkStateReceiver; - private NotificationManager mNotificationManager; - - private boolean mDisplayBytecount=false; - - private boolean mStarting=false; - - private long mConnecttime; - - - private static final int OPENVPN_STATUS = 1; - - public static final int PROTECT_FD = 0; - - private final IBinder mBinder = new LocalBinder(); - - public class LocalBinder extends Binder { - public OpenVpnService getService() { - // Return this instance of LocalService so clients can call public methods - return OpenVpnService.this; - } - } - - @Override - public IBinder onBind(Intent intent) { - String action = intent.getAction(); - if( action !=null && (action.equals(START_SERVICE) || action.equals(RETRIEVE_SERVICE)) ) - return mBinder; - else - return super.onBind(intent); - } - - @Override - public void onRevoke() { - OpenVpnManagementThread.stopOpenVPN(); - endVpnService(); - } - - // Similar to revoke but do not try to stop process - public void processDied() { - endVpnService(); - } - - private void endVpnService() { - mProcessThread=null; - OpenVPN.logBuilderConfig(null); - ProfileManager.setConntectedVpnProfileDisconnected(this); - if(!mStarting) { - stopSelf(); - stopForeground(true); - } - } - - private void showNotification(String state, String msg, String tickerText, boolean lowpriority, long when, boolean persistant) { - String ns = Context.NOTIFICATION_SERVICE; - mNotificationManager = (NotificationManager) getSystemService(ns); - int icon; - if (state.equals("NOPROCESS") || state.equals("AUTH_FAILED") || state.equals("NONETWORK") || state.equals("EXITING")){ - icon = R.drawable.ic_vpn_disconnected; - }else{ - icon = R.drawable.ic_stat_vpn; - } - - android.app.Notification.Builder nbuilder = new Notification.Builder(this); - - nbuilder.setContentTitle(getString(R.string.notifcation_title,mProfile.mLocation)); - nbuilder.setContentText(msg); - nbuilder.setOnlyAlertOnce(true); - nbuilder.setOngoing(persistant); - nbuilder.setContentIntent(getLogPendingIntent()); - nbuilder.setSmallIcon(icon); - if(when !=0) - nbuilder.setWhen(when); - - - // Try to set the priority available since API 16 (Jellybean) - jbNotificationExtras(lowpriority, nbuilder); - if(tickerText!=null) - nbuilder.setTicker(tickerText); - - @SuppressWarnings("deprecation") - Notification notification = nbuilder.getNotification(); - - - mNotificationManager.notify(OPENVPN_STATUS, notification); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private void jbNotificationExtras(boolean lowpriority, - android.app.Notification.Builder nbuilder) { - try { - if(lowpriority) { - Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class); - // PRIORITY_MIN == -2 - setpriority.invoke(nbuilder, -2 ); - - nbuilder.setUsesChronometer(true); - /* PendingIntent cancelconnet=null; - - nbuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel, - getString(R.string.cancel_connection),cancelconnet); */ - } - - //ignore exception - } catch (NoSuchMethodException nsm) { - } catch (IllegalArgumentException e) { - } catch (IllegalAccessException e) { - } catch (InvocationTargetException e) { - } - - } - - PendingIntent getLogPendingIntent() { - // Let the configure Button show the Dashboard - Intent intent = new Intent(Dashboard.getAppContext(),Dashboard.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - return startLW; - - } - - - private LocalServerSocket openManagmentInterface(int tries) { - // Could take a while to open connection - String socketname = (getCacheDir().getAbsolutePath() + "/" + "mgmtsocket"); - LocalSocket sock = new LocalSocket(); - - while(tries > 0 && !sock.isConnected()) { - try { - sock.bind(new LocalSocketAddress(socketname, - LocalSocketAddress.Namespace.FILESYSTEM)); - } catch (IOException e) { - // wait 300 ms before retrying - try { Thread.sleep(300); - } catch (InterruptedException e1) {} - - } - tries--; - } - - try { - LocalServerSocket lss = new LocalServerSocket(sock.getFileDescriptor()); - return lss; - } catch (IOException e) { - e.printStackTrace(); - } - return null; - - - } - - void registerNetworkStateReceiver() { - // Registers BroadcastReceiver to track network connection changes. - IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); - mNetworkStateReceiver = new NetworkSateReceiver(mSocketManager); - this.registerReceiver(mNetworkStateReceiver, filter); - } - - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - - if( intent != null && intent.getAction() !=null && - (intent.getAction().equals(START_SERVICE) || intent.getAction().equals(RETRIEVE_SERVICE)) ) - return START_NOT_STICKY; - - - // Extract information from the intent. - String prefix = getPackageName(); - String[] argv = intent.getStringArrayExtra(prefix + ".ARGV"); - String nativelibdir = intent.getStringExtra(prefix + ".nativelib"); - String profileUUID = intent.getStringExtra(prefix + ".profileUUID"); - - mProfile = ProfileManager.get(profileUUID); - - //showNotification("Starting VPN " + mProfile.mName,"Starting VPN " + mProfile.mName, false,0); - - - OpenVPN.addStateListener(this); - - // Set a flag that we are starting a new VPN - mStarting=true; - // Stop the previous session by interrupting the thread. - if(OpenVpnManagementThread.stopOpenVPN()){ - // an old was asked to exit, wait 2s - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - } - } - - if (mProcessThread!=null) { - mProcessThread.interrupt(); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - } - } - // An old running VPN should now be exited - mStarting=false; - - - // Open the Management Interface - LocalServerSocket mgmtsocket = openManagmentInterface(8); - - if(mgmtsocket!=null) { - // start a Thread that handles incoming messages of the managment socket - mSocketManager = new OpenVpnManagementThread(mProfile,mgmtsocket,this); - mSocketManagerThread = new Thread(mSocketManager,"OpenVPNMgmtThread"); - mSocketManagerThread.start(); - OpenVPN.logInfo("started Socket Thread"); - registerNetworkStateReceiver(); - } - - - // Start a new session by creating a new thread. - OpenVPNThread processThread = new OpenVPNThread(this, argv,nativelibdir); - - mProcessThread = new Thread(processThread, "OpenVPNProcessThread"); - mProcessThread.start(); - - ProfileManager.setConnectedVpnProfile(this, mProfile); - - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - if (mProcessThread != null) { - mSocketManager.managmentCommand("signal SIGINT\n"); - - mProcessThread.interrupt(); - } - if (mNetworkStateReceiver!= null) { - this.unregisterReceiver(mNetworkStateReceiver); - } - - } - - - - public ParcelFileDescriptor openTun() { - Builder builder = new Builder(); - - if(mLocalIP==null && mLocalIPv6==null) { - OpenVPN.logMessage(0, "", getString(R.string.opentun_no_ipaddr)); - return null; - } - - if(mLocalIP!=null) { - builder.addAddress(mLocalIP.mIp, mLocalIP.len); - } - - if(mLocalIPv6!=null) { - String[] ipv6parts = mLocalIPv6.split("/"); - builder.addAddress(ipv6parts[0],Integer.parseInt(ipv6parts[1])); - } - - - for (String dns : mDnslist ) { - try { - builder.addDnsServer(dns); - } catch (IllegalArgumentException iae) { - OpenVPN.logError(R.string.dns_add_error, dns,iae.getLocalizedMessage()); - } - } - - - builder.setMtu(mMtu); - - - for (CIDRIP route:mRoutes) { - try { - builder.addRoute(route.mIp, route.len); - } catch (IllegalArgumentException ia) { - OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage()); - } - } - - for(String v6route:mRoutesv6) { - try { - String[] v6parts = v6route.split("/"); - builder.addRoute(v6parts[0],Integer.parseInt(v6parts[1])); - } catch (IllegalArgumentException ia) { - OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + v6route + " " + ia.getLocalizedMessage()); - } - } - - if(mDomain!=null) - builder.addSearchDomain(mDomain); - - String bconfig[] = new String[6]; - - bconfig[0]= getString(R.string.last_openvpn_tun_config); - bconfig[1] = getString(R.string.local_ip_info,mLocalIP.mIp,mLocalIP.len,mLocalIPv6, mMtu); - bconfig[2] = getString(R.string.dns_server_info, joinString(mDnslist)); - bconfig[3] = getString(R.string.dns_domain_info, mDomain); - bconfig[4] = getString(R.string.routes_info, joinString(mRoutes)); - bconfig[5] = getString(R.string.routes_info6, joinString(mRoutesv6)); - - String session = mProfile.mLocation; - /* we don't want the IP address in the notification bar - if(mLocalIP!=null && mLocalIPv6!=null) - session = getString(R.string.session_ipv6string,session, mLocalIP, mLocalIPv6); - else if (mLocalIP !=null) - session= getString(R.string.session_ipv4string, session, mLocalIP); - */ - builder.setSession(session); - - - OpenVPN.logBuilderConfig(bconfig); - - // No DNS Server, log a warning - if(mDnslist.size()==0) - OpenVPN.logInfo(R.string.warn_no_dns); - - // Reset information - mDnslist.clear(); - mRoutes.clear(); - mRoutesv6.clear(); - mLocalIP=null; - mLocalIPv6=null; - mDomain=null; - - builder.setConfigureIntent(getLogPendingIntent()); - - try { - ParcelFileDescriptor pfd = builder.establish(); - return pfd; - } catch (Exception e) { - OpenVPN.logMessage(0, "", getString(R.string.tun_open_error)); - OpenVPN.logMessage(0, "", getString(R.string.error) + e.getLocalizedMessage()); - OpenVPN.logMessage(0, "", getString(R.string.tun_error_helpful)); - return null; - } - - } - - - // Ugly, but java has no such method - private String joinString(Vector vec) { - String ret = ""; - if(vec.size() > 0){ - ret = vec.get(0).toString(); - for(int i=1;i < vec.size();i++) { - ret = ret + ", " + vec.get(i).toString(); - } - } - return ret; - } - - - - - - - public void addDNS(String dns) { - mDnslist.add(dns); - } - - - public void setDomain(String domain) { - if(mDomain==null) { - mDomain=domain; - } - } - - - public void addRoute(String dest, String mask) { - CIDRIP route = new CIDRIP(dest, mask); - if(route.len == 32 && !mask.equals("255.255.255.255")) { - OpenVPN.logMessage(0, "", getString(R.string.route_not_cidr,dest,mask)); - } - - if(route.normalise()) - OpenVPN.logMessage(0, "", getString(R.string.route_not_netip,dest,route.len,route.mIp)); - - mRoutes.add(route); - } - - public void addRoutev6(String extra) { - mRoutesv6.add(extra); - } - - - public void setLocalIP(String local, String netmask,int mtu, String mode) { - mLocalIP = new CIDRIP(local, netmask); - mMtu = mtu; - - if(mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) { - // get the netmask as IP - long netint = CIDRIP.getInt(netmask); - if(Math.abs(netint - mLocalIP.getInt()) ==1) { - if(mode.equals("net30")) - mLocalIP.len=30; - else - mLocalIP.len=31; - } else { - OpenVPN.logMessage(0, "", getString(R.string.ip_not_cidr, local,netmask,mode)); - } - } - } - - public void setLocalIPv6(String ipv6addr) { - mLocalIPv6 = ipv6addr; - } - - public boolean isRunning() { - if (mStarting == true || mProcessThread != null) - return true; - else - return false; - } - - @Override - public void updateState(String state,String logmessage, int resid) { - // If the process is not running, ignore any state, - // Notification should be invisible in this state - if(mProcessThread==null) - return; - if("CONNECTED".equals(state)) { - mNotificationManager.cancel(OPENVPN_STATUS); - } else if(!"BYTECOUNT".equals(state)) { - - // Other notifications are shown, - // This also mean we are no longer connected, ignore bytecount messages until next - // CONNECTED - String ticker = getString(resid); - boolean persist = false; - if (("NOPROCESS".equals(state) ) || ("EXITING").equals(state)){ - showNotification(state, getString(R.string.eip_state_not_connected), ticker, false, 0, persist); - } - else if (state.equals("GET_CONFIG") || state.equals("ASSIGN_IP")){ //don't show them in the notification message - } - else{ - persist = true; - showNotification(state, getString(resid) +" " + logmessage,ticker,false,0,persist); - } - } - } - - @Override - public boolean handleMessage(Message msg) { - Runnable r = msg.getCallback(); - if(r!=null){ - r.run(); - return true; - } else { - return false; - } - } -} diff --git a/app/src/main/java/se/leap/openvpn/ProfileManager.java b/app/src/main/java/se/leap/openvpn/ProfileManager.java deleted file mode 100644 index b9eb3ab6..00000000 --- a/app/src/main/java/se/leap/openvpn/ProfileManager.java +++ /dev/null @@ -1,220 +0,0 @@ -package se.leap.openvpn; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.StreamCorruptedException; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.preference.PreferenceManager; - -public class ProfileManager { - private static final String PREFS_NAME = "VPNList"; - - - - private static final String ONBOOTPROFILE = "onBootProfile"; - - - - private static ProfileManager instance; - - - - private static VpnProfile mLastConnectedVpn=null; - private HashMap profiles=new HashMap(); - private static VpnProfile tmpprofile=null; - - - public static VpnProfile get(String key) { - if (tmpprofile!=null && tmpprofile.getUUIDString().equals(key)) - return tmpprofile; - - if(instance==null) - return null; - return instance.profiles.get(key); - - } - - - - private ProfileManager() { } - - private static void checkInstance(Context context) { - if(instance == null) { - instance = new ProfileManager(); - instance.loadVPNList(context); - } - } - - synchronized public static ProfileManager getInstance(Context context) { - checkInstance(context); - return instance; - } - - public static void setConntectedVpnProfileDisconnected(Context c) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); - Editor prefsedit = prefs.edit(); - prefsedit.putString(ONBOOTPROFILE, null); - prefsedit.apply(); - - } - - public static void setConnectedVpnProfile(Context c, VpnProfile connectedrofile) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); - Editor prefsedit = prefs.edit(); - - prefsedit.putString(ONBOOTPROFILE, connectedrofile.getUUIDString()); - prefsedit.apply(); - mLastConnectedVpn=connectedrofile; - - } - - public static VpnProfile getOnBootProfile(Context c) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); - - boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false); - - - String mBootProfileUUID = prefs.getString(ONBOOTPROFILE,null); - if(useStartOnBoot && mBootProfileUUID!=null) - return get(c, mBootProfileUUID); - else - return null; - } - - - - - public Collection getProfiles() { - return profiles.values(); - } - - public VpnProfile getProfileByName(String name) { - for (VpnProfile vpnp : profiles.values()) { - if(vpnp.getName().equals(name)) { - return vpnp; - } - } - return null; - } - - public void saveProfileList(Context context) { - SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); - Editor editor = sharedprefs.edit(); - editor.putStringSet("vpnlist", profiles.keySet()); - - // For reasing I do not understand at all - // Android saves my prefs file only one time - // if I remove the debug code below :( - int counter = sharedprefs.getInt("counter", 0); - editor.putInt("counter", counter+1); - editor.apply(); - - } - - public void addProfile(VpnProfile profile) { - profiles.put(profile.getUUID().toString(),profile); - - } - - public static void setTemporaryProfile(VpnProfile tmp) { - ProfileManager.tmpprofile = tmp; - } - - - public void saveProfile(Context context,VpnProfile profile) { - // First let basic settings save its state - - ObjectOutputStream vpnfile; - try { - vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"),Activity.MODE_PRIVATE)); - - vpnfile.writeObject(profile); - vpnfile.flush(); - vpnfile.close(); - } catch (FileNotFoundException e) { - - e.printStackTrace(); - throw new RuntimeException(e); - } catch (IOException e) { - - e.printStackTrace(); - throw new RuntimeException(e); - - } - } - - - private void loadVPNList(Context context) { - profiles = new HashMap(); - SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); - Set vlist = listpref.getStringSet("vpnlist", null); - Exception exp =null; - if(vlist==null){ - vlist = new HashSet(); - } - - for (String vpnentry : vlist) { - try { - ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); - VpnProfile vp = ((VpnProfile) vpnfile.readObject()); - - // Sanity check - if(vp==null || vp.mName==null || vp.getUUID()==null) - continue; - - profiles.put(vp.getUUID().toString(), vp); - - } catch (StreamCorruptedException e) { - exp=e; - } catch (FileNotFoundException e) { - exp=e; - } catch (IOException e) { - exp=e; - } catch (ClassNotFoundException e) { - exp=e; - } - if(exp!=null) { - exp.printStackTrace(); - } - } - } - - public int getNumberOfProfiles() { - return profiles.size(); - } - - - - public void removeProfile(Context context,VpnProfile profile) { - String vpnentry = profile.getUUID().toString(); - profiles.remove(vpnentry); - saveProfileList(context); - context.deleteFile(vpnentry + ".vp"); - if(mLastConnectedVpn==profile) - mLastConnectedVpn=null; - - } - - - - public static VpnProfile get(Context context, String profileUUID) { - checkInstance(context); - return get(profileUUID); - } - - - - public static VpnProfile getLastConnectedVpn() { - return mLastConnectedVpn; - } - -} diff --git a/app/src/main/java/se/leap/openvpn/ProxyDetection.java b/app/src/main/java/se/leap/openvpn/ProxyDetection.java deleted file mode 100644 index c7b3d196..00000000 --- a/app/src/main/java/se/leap/openvpn/ProxyDetection.java +++ /dev/null @@ -1,54 +0,0 @@ -package se.leap.openvpn; - -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.SocketAddress; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.List; - -import se.leap.bitmaskclient.R; - -public class ProxyDetection { - static SocketAddress detectProxy(VpnProfile vp) { - // Construct a new url with https as protocol - try { - URL url = new URL(String.format("https://%s:%s",vp.mServerName,vp.mServerPort)); - Proxy proxy = getFirstProxy(url); - - if(proxy==null) - return null; - SocketAddress addr = proxy.address(); - if (addr instanceof InetSocketAddress) { - return addr; - } - - } catch (MalformedURLException e) { - OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage()); - } catch (URISyntaxException e) { - OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage()); - } - return null; - } - - static Proxy getFirstProxy(URL url) throws URISyntaxException { - System.setProperty("java.net.useSystemProxies", "true"); - - List proxylist = ProxySelector.getDefault().select(url.toURI()); - - - if (proxylist != null) { - for (Proxy proxy: proxylist) { - SocketAddress addr = proxy.address(); - - if (addr != null) { - return proxy; - } - } - - } - return null; - } -} \ No newline at end of file diff --git a/app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java b/app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java deleted file mode 100644 index 418cf7e9..00000000 --- a/app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java +++ /dev/null @@ -1,76 +0,0 @@ -package se.leap.openvpn; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import se.leap.bitmaskclient.R; - -import android.content.Context; -import android.content.Intent; -import android.os.Build; - -public class VPNLaunchHelper { - static private boolean writeMiniVPN(Context context) { - File mvpnout = new File(context.getCacheDir(),VpnProfile.MINIVPN); - if (mvpnout.exists() && mvpnout.canExecute()) - return true; - - IOException e2 = null; - - try { - InputStream mvpn; - - try { - mvpn = context.getAssets().open("minivpn." + Build.CPU_ABI); - } - catch (IOException errabi) { - OpenVPN.logInfo("Failed getting assets for archicture " + Build.CPU_ABI); - e2=errabi; - mvpn = context.getAssets().open("minivpn." + Build.CPU_ABI2); - - } - - - FileOutputStream fout = new FileOutputStream(mvpnout); - - byte buf[]= new byte[4096]; - - int lenread = mvpn.read(buf); - while(lenread> 0) { - fout.write(buf, 0, lenread); - lenread = mvpn.read(buf); - } - fout.close(); - - if(!mvpnout.setExecutable(true)) { - OpenVPN.logMessage(0, "","Failed to set minivpn executable"); - return false; - } - - - return true; - } catch (IOException e) { - if(e2!=null) - OpenVPN.logMessage(0, "",e2.getLocalizedMessage()); - OpenVPN.logMessage(0, "",e.getLocalizedMessage()); - e.printStackTrace(); - return false; - } - } - - - public static void startOpenVpn(VpnProfile startprofile, Context context) { - if(!writeMiniVPN(context)) { - OpenVPN.logMessage(0, "", "Error writing minivpn binary"); - return; - } - OpenVPN.logMessage(0, "", context.getString(R.string.building_configration)); - - Intent startVPN = startprofile.prepareIntent(context); - if(startVPN!=null) - context.startService(startVPN); - - } -} diff --git a/app/src/main/java/se/leap/openvpn/VpnProfile.java b/app/src/main/java/se/leap/openvpn/VpnProfile.java deleted file mode 100644 index 481819ad..00000000 --- a/app/src/main/java/se/leap/openvpn/VpnProfile.java +++ /dev/null @@ -1,758 +0,0 @@ -package se.leap.openvpn; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Collection; -import java.util.UUID; -import java.util.Vector; - -import org.spongycastle.util.io.pem.PemObject; -import org.spongycastle.util.io.pem.PemWriter; - -import se.leap.bitmaskclient.ConfigHelper; -import se.leap.bitmaskclient.Dashboard; -import se.leap.bitmaskclient.EIP; -import se.leap.bitmaskclient.Provider; -import se.leap.bitmaskclient.R; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; -import android.preference.PreferenceManager; -import android.security.KeyChain; -import android.security.KeyChainException; - -public class VpnProfile implements Serializable{ - // Parcable - /** - * - */ - private static final long serialVersionUID = 7085688938959334563L; - static final int TYPE_CERTIFICATES=0; - static final int TYPE_PKCS12=1; - static final int TYPE_KEYSTORE=2; - public static final int TYPE_USERPASS = 3; - public static final int TYPE_STATICKEYS = 4; - public static final int TYPE_USERPASS_CERTIFICATES = 5; - public static final int TYPE_USERPASS_PKCS12 = 6; - public static final int TYPE_USERPASS_KEYSTORE = 7; - - // Don't change this, not all parts of the program use this constant - public static final String EXTRA_PROFILEUUID = "se.leap.bitmaskclient.profileUUID"; // TODO this feels wrong. See Issue #1494 - public static final String INLINE_TAG = "[[INLINE]]"; - private static final String OVPNCONFIGFILE = "android.conf"; - - protected transient String mTransientPW=null; - protected transient String mTransientPCKS12PW=null; - private transient PrivateKey mPrivateKey; - protected boolean profileDleted=false; - - - public static String DEFAULT_DNS1="131.234.137.23"; - public static String DEFAULT_DNS2="131.234.137.24"; - - // Public attributes, since I got mad with getter/setter - // set members to default values - private UUID mUuid; - public int mAuthenticationType = TYPE_CERTIFICATES ; - public String mName; - public String mLocation; - public String mAlias; - public String mClientCertFilename; - public String mTLSAuthDirection=""; - public String mTLSAuthFilename; - public String mClientKeyFilename; - public String mCaFilename; - public boolean mUseLzo=true; - public String mServerPort= "1194" ; - public boolean mUseUdp = true; - public String mPKCS12Filename; - public String mPKCS12Password; - public boolean mUseTLSAuth = false; - public String mServerName = "openvpn.blinkt.de" ; - public String mDNS1=DEFAULT_DNS1; - public String mDNS2=DEFAULT_DNS2; - public String mIPv4Address; - public String mIPv6Address; - public boolean mOverrideDNS=false; - public String mSearchDomain="blinkt.de"; - public boolean mUseDefaultRoute=true; - public boolean mUsePull=true; - public String mCustomRoutes; - public boolean mCheckRemoteCN=false; - public boolean mExpectTLSCert=true; - public String mRemoteCN=""; - public String mPassword=""; - public String mUsername=""; - public boolean mRoutenopull=false; - public boolean mUseRandomHostname=false; - public boolean mUseFloat=false; - public boolean mUseCustomConfig=false; - public String mCustomConfigOptions=""; - public String mVerb="1"; - public String mCipher=""; - public boolean mNobind=false; - public boolean mUseDefaultRoutev6=true; - public String mCustomRoutesv6=""; - public String mKeyPassword=""; - public boolean mPersistTun = false; - public String mConnectRetryMax="5"; - public String mConnectRetry="10"; - public boolean mUserEditable=true; - - static final String MINIVPN = "miniopenvpn"; - - - - - - - public void clearDefaults() { - mServerName="unkown"; - mUsePull=false; - mUseLzo=false; - mUseDefaultRoute=false; - mUseDefaultRoutev6=false; - mExpectTLSCert=false; - mPersistTun = false; - } - - - public static String openVpnEscape(String unescaped) { - if(unescaped==null) - return null; - String escapedString = unescaped.replace("\\", "\\\\"); - escapedString = escapedString.replace("\"","\\\""); - escapedString = escapedString.replace("\n","\\n"); - - if (escapedString.equals(unescaped) && !escapedString.contains(" ") && !escapedString.contains("#")) - return unescaped; - else - return '"' + escapedString + '"'; - } - - - static final String OVPNCONFIGCA = "android-ca.pem"; - static final String OVPNCONFIGUSERCERT = "android-user.pem"; - - - public VpnProfile(String name) { - mUuid = UUID.randomUUID(); - mName = name; - } - - public UUID getUUID() { - return mUuid; - - } - - public String getName() { - return mName; - } - - - public String getConfigFile(Context context) - { - - File cacheDir= context.getCacheDir(); - String cfg=""; - - // Enable managment interface - cfg += "# Enables connection to GUI\n"; - cfg += "management "; - - cfg +=cacheDir.getAbsolutePath() + "/" + "mgmtsocket"; - cfg += " unix\n"; - cfg += "management-client\n"; - // Not needed, see updated man page in 2.3 - //cfg += "management-signal\n"; - cfg += "management-query-passwords\n"; - cfg += "management-hold\n\n"; - - /* tmp-dir patched out :) - cfg+="# /tmp does not exist on Android\n"; - cfg+="tmp-dir "; - cfg+=cacheDir.getAbsolutePath(); - cfg+="\n\n"; */ - - cfg+="# Log window is better readable this way\n"; - cfg+="suppress-timestamps\n"; - - - - boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS); - - if(useTLSClient && mUsePull) - cfg+="client\n"; - else if (mUsePull) - cfg+="pull\n"; - else if(useTLSClient) - cfg+="tls-client\n"; - - - cfg+="verb " + mVerb + "\n"; - - if(mConnectRetryMax ==null) { - mConnectRetryMax="5"; - } - - if(!mConnectRetryMax.equals("-1")) - cfg+="connect-retry-max " + mConnectRetryMax+ "\n"; - - if(mConnectRetry==null) - mConnectRetry="10"; - - - cfg+="connect-retry " + mConnectRetry + "\n"; - - cfg+="resolv-retry 60\n"; - - - - // We cannot use anything else than tun - cfg+="dev tun\n"; - - // Server Address - cfg+="remote "; - cfg+=mServerName; - cfg+=" "; - cfg+=mServerPort; - if(mUseUdp) - cfg+=" udp\n"; - else - cfg+=" tcp-client\n"; - - - - - switch(mAuthenticationType) { - case VpnProfile.TYPE_USERPASS_CERTIFICATES: - cfg+="auth-user-pass\n"; - case VpnProfile.TYPE_CERTIFICATES: - /*// Ca - cfg+=insertFileData("ca",mCaFilename); - - // Client Cert + Key - cfg+=insertFileData("key",mClientKeyFilename); - cfg+=insertFileData("cert",mClientCertFilename); -*/ - // FIXME This is all we need...The whole switch statement can go... - SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, context.MODE_PRIVATE); - cfg+="\n"+preferences.getString(Provider.CA_CERT, "")+"\n\n"; - cfg+="\n"+preferences.getString(EIP.PRIVATE_KEY, "")+"\n\n"; - cfg+="\n"+preferences.getString(EIP.CERTIFICATE, "")+"\n\n"; - - break; - case VpnProfile.TYPE_USERPASS_PKCS12: - cfg+="auth-user-pass\n"; - case VpnProfile.TYPE_PKCS12: - cfg+=insertFileData("pkcs12",mPKCS12Filename); - break; - - case VpnProfile.TYPE_USERPASS_KEYSTORE: - cfg+="auth-user-pass\n"; - case VpnProfile.TYPE_KEYSTORE: - cfg+="ca " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGCA + "\n"; - cfg+="cert " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGUSERCERT + "\n"; - cfg+="management-external-key\n"; - - break; - case VpnProfile.TYPE_USERPASS: - cfg+="auth-user-pass\n"; - cfg+=insertFileData("ca",mCaFilename); - } - - if(mUseLzo) { - cfg+="comp-lzo\n"; - } - - if(mUseTLSAuth) { - if(mAuthenticationType==TYPE_STATICKEYS) - cfg+=insertFileData("secret",mTLSAuthFilename); - else - cfg+=insertFileData("tls-auth",mTLSAuthFilename); - - if(nonNull(mTLSAuthDirection)) { - cfg+= "key-direction "; - cfg+= mTLSAuthDirection; - cfg+="\n"; - } - - } - - if(!mUsePull ) { - if(nonNull(mIPv4Address)) - cfg +="ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n"; - - if(nonNull(mIPv6Address)) - cfg +="ifconfig-ipv6 " + mIPv6Address + "\n"; - } - - if(mUsePull && mRoutenopull) - cfg += "route-nopull\n"; - - String routes = ""; - int numroutes=0; - if(mUseDefaultRoute) - routes += "route 0.0.0.0 0.0.0.0\n"; - else - for(String route:getCustomRoutes()) { - routes += "route " + route + "\n"; - numroutes++; - } - - - if(mUseDefaultRoutev6) - cfg += "route-ipv6 ::/0\n"; - else - for(String route:getCustomRoutesv6()) { - routes += "route-ipv6 " + route + "\n"; - numroutes++; - } - - // Round number to next 100 - if(numroutes> 90) { - numroutes = ((numroutes / 100)+1) * 100; - cfg+="# Alot of routes are set, increase max-routes\n"; - cfg+="max-routes " + numroutes + "\n"; - } - cfg+=routes; - - if(mOverrideDNS || !mUsePull) { - if(nonNull(mDNS1)) - cfg+="dhcp-option DNS " + mDNS1 + "\n"; - if(nonNull(mDNS2)) - cfg+="dhcp-option DNS " + mDNS2 + "\n"; - if(nonNull(mSearchDomain)) - cfg+="dhcp-option DOMAIN " + mSearchDomain + "\n"; - - } - - if(mNobind) - cfg+="nobind\n"; - - - - // Authentication - if(mCheckRemoteCN) { - if(mRemoteCN == null || mRemoteCN.equals("") ) - cfg+="tls-remote " + mServerName + "\n"; - else - cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n"; - } - if(mExpectTLSCert) - cfg += "remote-cert-tls server\n"; - - - if(nonNull(mCipher)){ - cfg += "cipher " + mCipher + "\n"; - } - - - // Obscure Settings dialog - if(mUseRandomHostname) - cfg += "#my favorite options :)\nremote-random-hostname\n"; - - if(mUseFloat) - cfg+= "float\n"; - - if(mPersistTun) { - cfg+= "persist-tun\n"; - cfg+= "# persist-tun also sets persist-remote-ip to avoid DNS resolve problem\n"; - cfg+= "persist-remote-ip\n"; - } - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true); - if(usesystemproxy) { - cfg+= "# Use system proxy setting\n"; - cfg+= "management-query-proxy\n"; - } - - - if(mUseCustomConfig) { - cfg += "# Custom configuration options\n"; - cfg += "# You are on your on own here :)\n"; - cfg += mCustomConfigOptions; - cfg += "\n"; - - } - - - - return cfg; - } - - //! Put inline data inline and other data as normal escaped filename - private String insertFileData(String cfgentry, String filedata) { - if(filedata==null) { - return String.format("%s %s\n",cfgentry,"missing"); - }else if(filedata.startsWith(VpnProfile.INLINE_TAG)){ - String datawoheader = filedata.substring(VpnProfile.INLINE_TAG.length()); - return String.format("<%s>\n%s\n\n",cfgentry,datawoheader,cfgentry); - } else { - return String.format("%s %s\n",cfgentry,openVpnEscape(filedata)); - } - } - - private boolean nonNull(String val) { - if(val == null || val.equals("")) - return false; - else - return true; - } - - private Collection getCustomRoutes() { - Vector cidrRoutes=new Vector(); - if(mCustomRoutes==null) { - // No routes set, return empty vector - return cidrRoutes; - } - for(String route:mCustomRoutes.split("[\n \t]")) { - if(!route.equals("")) { - String cidrroute = cidrToIPAndNetmask(route); - if(cidrRoutes == null) - return null; - - cidrRoutes.add(cidrroute); - } - } - - return cidrRoutes; - } - - private Collection getCustomRoutesv6() { - Vector cidrRoutes=new Vector(); - if(mCustomRoutesv6==null) { - // No routes set, return empty vector - return cidrRoutes; - } - for(String route:mCustomRoutesv6.split("[\n \t]")) { - if(!route.equals("")) { - cidrRoutes.add(route); - } - } - - return cidrRoutes; - } - - - - private String cidrToIPAndNetmask(String route) { - String[] parts = route.split("/"); - - // No /xx, assume /32 as netmask - if (parts.length ==1) - parts = (route + "/32").split("/"); - - if (parts.length!=2) - return null; - int len; - try { - len = Integer.parseInt(parts[1]); - } catch(NumberFormatException ne) { - return null; - } - if (len <0 || len >32) - return null; - - - long nm = 0xffffffffl; - nm = (nm << (32-len)) & 0xffffffffl; - - String netmask =String.format("%d.%d.%d.%d", (nm & 0xff000000) >> 24,(nm & 0xff0000) >> 16, (nm & 0xff00) >> 8 ,nm & 0xff ); - return parts[0] + " " + netmask; - } - - - - private String[] buildOpenvpnArgv(File cacheDir) - { - Vector args = new Vector(); - - // Add fixed paramenters - //args.add("/data/data/se.leap.openvpn/lib/openvpn"); - args.add(cacheDir.getAbsolutePath() +"/" + VpnProfile.MINIVPN); - - args.add("--config"); - args.add(cacheDir.getAbsolutePath() + "/" + OVPNCONFIGFILE); - // Silences script security warning - - args.add("script-security"); - args.add("0"); - - - return (String[]) args.toArray(new String[args.size()]); - } - - public Intent prepareIntent(Context context) { - String prefix = context.getPackageName(); - - Intent intent = new Intent(context,OpenVpnService.class); - - if(mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { - /*if(!saveCertificates(context)) - return null;*/ - } - - intent.putExtra(prefix + ".ARGV" , buildOpenvpnArgv(context.getCacheDir())); - intent.putExtra(prefix + ".profileUUID", mUuid.toString()); - - ApplicationInfo info = context.getApplicationInfo(); - intent.putExtra(prefix +".nativelib",info.nativeLibraryDir); - - try { - FileWriter cfg = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE); - cfg.write(getConfigFile(context)); - cfg.flush(); - cfg.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - return intent; - } - - private boolean saveCertificates(Context context) { - PrivateKey privateKey = null; - X509Certificate[] cachain=null; - try { - privateKey = KeyChain.getPrivateKey(context,mAlias); - mPrivateKey = privateKey; - - cachain = KeyChain.getCertificateChain(context, mAlias); - if(cachain.length <= 1 && !nonNull(mCaFilename)) - OpenVPN.logMessage(0, "", context.getString(R.string.keychain_nocacert)); - - for(X509Certificate cert:cachain) { - OpenVPN.logInfo(R.string.cert_from_keystore,cert.getSubjectDN()); - } - - - - - if(nonNull(mCaFilename)) { - try { - Certificate cacert = getCacertFromFile(); - X509Certificate[] newcachain = new X509Certificate[cachain.length+1]; - for(int i=0;i= 1){ - X509Certificate usercert = cachain[0]; - - FileWriter userout = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + VpnProfile.OVPNCONFIGUSERCERT); - - PemWriter upw = new PemWriter(userout); - upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded())); - upw.close(); - - } - - return true; - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (CertificateException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (KeyChainException e) { - OpenVPN.logMessage(0,"",context.getString(R.string.keychain_access)); - } - return false; - } - private Certificate getCacertFromFile() throws FileNotFoundException, CertificateException { - CertificateFactory certFact = CertificateFactory.getInstance("X.509"); - - InputStream inStream; - - if(mCaFilename.startsWith(INLINE_TAG)) - inStream = new ByteArrayInputStream(mCaFilename.replace(INLINE_TAG,"").getBytes()); - else - inStream = new FileInputStream(mCaFilename); - - return certFact.generateCertificate(inStream); - } - - - //! Return an error if somethign is wrong - public int checkProfile(Context context) { -/* if(mAuthenticationType==TYPE_KEYSTORE || mAuthenticationType==TYPE_USERPASS_KEYSTORE) { - if(mAlias==null) - return R.string.no_keystore_cert_selected; - }*/ - - if(!mUsePull) { - if(mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null) - return R.string.ipv4_format_error; - } - if(isUserPWAuth() && !nonNull(mUsername)) { - return R.string.error_empty_username; - } - if(!mUseDefaultRoute && getCustomRoutes()==null) - return R.string.custom_route_format_error; - - // Everything okay - return R.string.no_error_found; - - } - - //! Openvpn asks for a "Private Key", this should be pkcs12 key - // - public String getPasswordPrivateKey() { - if(mTransientPCKS12PW!=null) { - String pwcopy = mTransientPCKS12PW; - mTransientPCKS12PW=null; - return pwcopy; - } - switch (mAuthenticationType) { - case TYPE_PKCS12: - case TYPE_USERPASS_PKCS12: - return mPKCS12Password; - - case TYPE_CERTIFICATES: - case TYPE_USERPASS_CERTIFICATES: - return mKeyPassword; - - case TYPE_USERPASS: - case TYPE_STATICKEYS: - default: - return null; - } - } - private boolean isUserPWAuth() { - switch(mAuthenticationType) { - case TYPE_USERPASS: - case TYPE_USERPASS_CERTIFICATES: - case TYPE_USERPASS_KEYSTORE: - case TYPE_USERPASS_PKCS12: - return true; - default: - return false; - - } - } - - - public boolean requireTLSKeyPassword() { - if(!nonNull(mClientKeyFilename)) - return false; - - String data = ""; - if(mClientKeyFilename.startsWith(INLINE_TAG)) - data = mClientKeyFilename; - else { - char[] buf = new char[2048]; - FileReader fr; - try { - fr = new FileReader(mClientKeyFilename); - int len = fr.read(buf); - while(len > 0 ) { - data += new String(buf,0,len); - len = fr.read(buf); - } - fr.close(); - } catch (FileNotFoundException e) { - return false; - } catch (IOException e) { - return false; - } - - } - - if(data.contains("Proc-Type: 4,ENCRYPTED")) - return true; - else if(data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----")) - return true; - else - return false; - } - - public int needUserPWInput() { - if((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12)&& - (mPKCS12Password == null || mPKCS12Password.equals(""))) { - if(mTransientPCKS12PW==null) - return R.string.pkcs12_file_encryption_key; - } - - if(mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) { - if(requireTLSKeyPassword() && !nonNull(mKeyPassword)) - if(mTransientPCKS12PW==null) { - return R.string.private_key_password; - } - } - - if(isUserPWAuth() && (mPassword.equals("") || mPassword == null)) { - if(mTransientPW==null) - return R.string.password; - - } - return 0; - } - - public String getPasswordAuth() { - if(mTransientPW!=null) { - String pwcopy = mTransientPW; - mTransientPW=null; - return pwcopy; - } else { - return mPassword; - } - } - - - // Used by the Array Adapter - @Override - public String toString() { - return mName; - } - - - public String getUUIDString() { - return mUuid.toString(); - } - - - public PrivateKey getKeystoreKey() { - return mPrivateKey; - } - - - -} - - - - diff --git a/app/src/main/res/drawable-hdpi/ic_menu_add.png b/app/src/main/res/drawable-hdpi/ic_menu_add.png deleted file mode 100644 index 444e8a5e..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_menu_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_login.png b/app/src/main/res/drawable-hdpi/ic_menu_login.png deleted file mode 100644 index afa152b2..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_menu_login.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png b/app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png deleted file mode 100644 index 577e0558..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_trash_holo_light.png b/app/src/main/res/drawable-hdpi/ic_menu_trash_holo_light.png deleted file mode 100644 index c62295aa..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_menu_trash_holo_light.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_sysbar_quicksettings.png b/app/src/main/res/drawable-hdpi/ic_sysbar_quicksettings.png deleted file mode 100644 index 47b4ba23..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_sysbar_quicksettings.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png b/app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png deleted file mode 100644 index dfb962b9..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/icon.png b/app/src/main/res/drawable-hdpi/icon.png deleted file mode 100644 index 02ede650..00000000 Binary files a/app/src/main/res/drawable-hdpi/icon.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/ic_menu_add.png b/app/src/main/res/drawable-ldpi/ic_menu_add.png deleted file mode 100644 index 89620af8..00000000 Binary files a/app/src/main/res/drawable-ldpi/ic_menu_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/ic_menu_login.png b/app/src/main/res/drawable-ldpi/ic_menu_login.png deleted file mode 100644 index d4181de5..00000000 Binary files a/app/src/main/res/drawable-ldpi/ic_menu_login.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/ic_stat_vpn.png b/app/src/main/res/drawable-ldpi/ic_stat_vpn.png deleted file mode 100644 index f973015c..00000000 Binary files a/app/src/main/res/drawable-ldpi/ic_stat_vpn.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/ic_vpn_disconnected.png b/app/src/main/res/drawable-ldpi/ic_vpn_disconnected.png deleted file mode 100644 index 22f3497e..00000000 Binary files a/app/src/main/res/drawable-ldpi/ic_vpn_disconnected.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/icon.png b/app/src/main/res/drawable-ldpi/icon.png deleted file mode 100644 index e312075d..00000000 Binary files a/app/src/main/res/drawable-ldpi/icon.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_add.png b/app/src/main/res/drawable-mdpi/ic_menu_add.png deleted file mode 100644 index 361c7c46..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_menu_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_settings_holo_light.png b/app/src/main/res/drawable-mdpi/ic_menu_settings_holo_light.png deleted file mode 100644 index f32a37e4..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_menu_settings_holo_light.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_trash_holo_light.png b/app/src/main/res/drawable-mdpi/ic_menu_trash_holo_light.png deleted file mode 100644 index 08291855..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_menu_trash_holo_light.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_sysbar_quicksettings.png b/app/src/main/res/drawable-mdpi/ic_sysbar_quicksettings.png deleted file mode 100644 index 79281042..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_sysbar_quicksettings.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_vpn_disconnected.png b/app/src/main/res/drawable-mdpi/ic_vpn_disconnected.png deleted file mode 100644 index f8b02bfb..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_vpn_disconnected.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/icon.png b/app/src/main/res/drawable-mdpi/icon.png deleted file mode 100644 index 468314c6..00000000 Binary files a/app/src/main/res/drawable-mdpi/icon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_login.png b/app/src/main/res/drawable-xhdpi/ic_menu_login.png deleted file mode 100644 index 5095ed97..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_menu_login.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_settings_holo_light.png b/app/src/main/res/drawable-xhdpi/ic_menu_settings_holo_light.png deleted file mode 100644 index aa33c388..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_menu_settings_holo_light.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_trash_holo_light.png b/app/src/main/res/drawable-xhdpi/ic_menu_trash_holo_light.png deleted file mode 100644 index bd3fd784..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_menu_trash_holo_light.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_sysbar_quicksettings.png b/app/src/main/res/drawable-xhdpi/ic_sysbar_quicksettings.png deleted file mode 100644 index a057db8b..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_sysbar_quicksettings.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_vpn_disconnected.png b/app/src/main/res/drawable-xhdpi/ic_vpn_disconnected.png deleted file mode 100644 index 7f44c46f..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_vpn_disconnected.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/icon.png b/app/src/main/res/drawable-xhdpi/icon.png deleted file mode 100644 index ead03720..00000000 Binary files a/app/src/main/res/drawable-xhdpi/icon.png and /dev/null differ diff --git a/app/src/main/res/layout-xlarge/about.xml b/app/src/main/res/layout-xlarge/about.xml deleted file mode 100644 index 6ab88737..00000000 --- a/app/src/main/res/layout-xlarge/about.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-xlarge/client_dashboard.xml b/app/src/main/res/layout-xlarge/client_dashboard.xml deleted file mode 100644 index bd644e1e..00000000 --- a/app/src/main/res/layout-xlarge/client_dashboard.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-xlarge/configuration_wizard_activity.xml b/app/src/main/res/layout-xlarge/configuration_wizard_activity.xml deleted file mode 100644 index bb169e00..00000000 --- a/app/src/main/res/layout-xlarge/configuration_wizard_activity.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/eip_service_fragment.xml b/app/src/main/res/layout-xlarge/eip_service_fragment.xml deleted file mode 100644 index e5c7f23d..00000000 --- a/app/src/main/res/layout-xlarge/eip_service_fragment.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-xlarge/log_in_dialog.xml b/app/src/main/res/layout-xlarge/log_in_dialog.xml deleted file mode 100644 index 3a9eebb8..00000000 --- a/app/src/main/res/layout-xlarge/log_in_dialog.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/logwindow.xml b/app/src/main/res/layout-xlarge/logwindow.xml deleted file mode 100644 index 4051c92c..00000000 --- a/app/src/main/res/layout-xlarge/logwindow.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/new_provider_dialog.xml b/app/src/main/res/layout-xlarge/new_provider_dialog.xml deleted file mode 100644 index fc7d84ab..00000000 --- a/app/src/main/res/layout-xlarge/new_provider_dialog.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/provider_detail_fragment.xml b/app/src/main/res/layout-xlarge/provider_detail_fragment.xml deleted file mode 100644 index 4abbaa17..00000000 --- a/app/src/main/res/layout-xlarge/provider_detail_fragment.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/provider_list_fragment.xml b/app/src/main/res/layout-xlarge/provider_list_fragment.xml deleted file mode 100644 index 59dd37d1..00000000 --- a/app/src/main/res/layout-xlarge/provider_list_fragment.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout-xlarge/provider_list_item.xml b/app/src/main/res/layout-xlarge/provider_list_item.xml deleted file mode 100644 index ec5db117..00000000 --- a/app/src/main/res/layout-xlarge/provider_list_item.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml deleted file mode 100644 index 4b3f16e0..00000000 --- a/app/src/main/res/layout/about.xml +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/client_dashboard.xml b/app/src/main/res/layout/client_dashboard.xml deleted file mode 100644 index a5387efd..00000000 --- a/app/src/main/res/layout/client_dashboard.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/configuration_wizard_activity.xml b/app/src/main/res/layout/configuration_wizard_activity.xml deleted file mode 100644 index f3d0e48b..00000000 --- a/app/src/main/res/layout/configuration_wizard_activity.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/eip_service_fragment.xml b/app/src/main/res/layout/eip_service_fragment.xml deleted file mode 100644 index 5992a873..00000000 --- a/app/src/main/res/layout/eip_service_fragment.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/log_in_dialog.xml b/app/src/main/res/layout/log_in_dialog.xml deleted file mode 100644 index c8a2f0a8..00000000 --- a/app/src/main/res/layout/log_in_dialog.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/logwindow.xml b/app/src/main/res/layout/logwindow.xml deleted file mode 100644 index 4051c92c..00000000 --- a/app/src/main/res/layout/logwindow.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/new_provider_dialog.xml b/app/src/main/res/layout/new_provider_dialog.xml deleted file mode 100644 index 19b8f442..00000000 --- a/app/src/main/res/layout/new_provider_dialog.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/provider_detail_fragment.xml b/app/src/main/res/layout/provider_detail_fragment.xml deleted file mode 100644 index eb90fad9..00000000 --- a/app/src/main/res/layout/provider_detail_fragment.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/provider_list_fragment.xml b/app/src/main/res/layout/provider_list_fragment.xml deleted file mode 100644 index 70dbae0d..00000000 --- a/app/src/main/res/layout/provider_list_fragment.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout/provider_list_item.xml b/app/src/main/res/layout/provider_list_item.xml deleted file mode 100644 index 8746f6f8..00000000 --- a/app/src/main/res/layout/provider_list_item.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/menu/client_dashboard.xml b/app/src/main/res/menu/client_dashboard.xml deleted file mode 100644 index 676c07c7..00000000 --- a/app/src/main/res/menu/client_dashboard.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/menu/configuration_wizard_activity.xml b/app/src/main/res/menu/configuration_wizard_activity.xml deleted file mode 100644 index 9936b6dc..00000000 --- a/app/src/main/res/menu/configuration_wizard_activity.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/logmenu.xml b/app/src/main/res/menu/logmenu.xml deleted file mode 100644 index c4087585..00000000 --- a/app/src/main/res/menu/logmenu.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/values-ca/arrays.xml b/app/src/main/res/values-ca/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-ca/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml deleted file mode 100644 index 2b250da1..00000000 --- a/app/src/main/res/values-ca/strings.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - Cancel·la - Quan a - No s\'ha trobat cap error - S\'ha trobat un error a la configuració - Conecta a la VPN - No s\'ha trobat el perfil especificat a l\'accès directe - La ruta ha estat refusas per Android - Desconecta - Neteja el registre - Cancela la confirmació - Desconcta la conexió VPN/cancela l\'intent de conexió? - Edita la configuració VPN - "Error: " - Neteja - info - Mostra els detalls de conexió - Servidor DNS: %s - Domini DNS: %s - Rutes: %s - Rutes IPv6: %s - Envia el fitxer de registre - Envia - Fitxer de registre de LEAP Android - S\'ha copiat l\'entrada al porta-retalls - Esperant el missatge d\'estat... - Perfil importat - Perfil importat %d - Contrasenya de la clau privada - Contrasenya - Construint la configuració... - Estat de la xarxa %s - Traducció al catala per Sergi Almacellas -<sergi@koolpi.com> - Utilitzant el proxy %1$s %2$d - Ignorar - Reinicia - Els canvis de configuració s\'apliquen desprès de reinicar la VPN. (Re)inicar la VPN ara? - S\'ha canviat la configuració - diff --git a/app/src/main/res/values-cs/arrays.xml b/app/src/main/res/values-cs/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-cs/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml deleted file mode 100644 index 68c5aeaf..00000000 --- a/app/src/main/res/values-cs/strings.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - Storno - Zdrojové kódy a seznam problémů je na https://github.com/leapcode/bitmask_android/ - Tento program používá následující komponenty; viz zdrojový kód pro detaily o licenci - O programu - Bez chyb - Chyba v konfiguraci - Chyba při zpracování IPv4 adresy - Chyba při zpracování vlastního směrování - Profil zvolený ve zkratce nenalezen - Směrování odmítnuto Androidem - Odpojit - vymazat log - Zrušit potvrzení - Odpojit/Zrušit připojování? - Změnit nastavení VPN - Na některých ICS systémech může být oprávnění pro /dev/tun špatně nastavené, nebo tun modul může zcela chybět. - Chyba při otvírání tun zařízení - "Chyba: " - Vymazat - info - Zobrazit detaily spojení - Poslední nastavení rozhraní pro OpenVPN: - Místní IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS server: %s - DNS doména: %s - Směrování: %s - Směrování IPv6: %s - Získány informace o rozhraní %1$s a %2$s, předpokládám, že druhá adresa je adresa vzdáleného kolegy. Používám /32 masku pro místní IP adresu. Mód OpenVPN je \"%3$s\". - %1$s a %2$s jako IP adresy s CIDR maskou nedávají smysl, používám /32 jako masku. - Směrování opraveno z %1$s/%2$s na %3$s/%2$s - Nelze přistoupit k Androidímu úložišti certifikátů. To může být způsobeno aktualizací firmwaru nebo obnovováním aplikace a jejího nastavení ze zálohy. Uprav VPN profil a znovu vyber certifikát pro vytvoření patřičných povolení. - %1$s %2$s - Odeslat soubor s logem - Odeslat - LEAP Android logovací soubor - Záznam z logu zkopírován do schránky - Tvůj obrázek není podporovaný rozhraním VPNService, je mi líto :-( - Odmítám otevřít tun zařízení bez informace o IP - Čekám na zprávu o stavu… - importovaný profil - importovaný profil %d - Jméno nesmí být prázdné. - Šifrovací klíč PKCS12 - Heslo k soukromému klíči - Heslo - Vytvářím konfiguraci… - Získán certifikát \'%s\' z úložiště - Stav sítě: %s - Žádný CA certifikát nebyl získán z úložiště, autentikace pravděpodobně selže. - Spuštěno na %1$s (%2$s) %3$s, Android API %4$d - Chyba při podepisování klíčem %1$s: %2$s - Českou lokalizaci zpracoval Jan Baier <baier.jan@gmail.com> - Není použit DNS server. Překlad jmen nemusí fungovat. Zvaž nastavení vlastního DNS serveru. - Nemohu přidat DNS server \"%1$s\", odmítnuto systémem: %2$s - Chyba při zjišťování nastavení proxy: %s - Používám proxy %1$s %2$d - Ignorovat - Restartovat - Změna nastavení začne platit až po restartu VPN. Restartovat teď? - Nastavení změněno - OpenVPN neočekávaně havarovalo. Zvaž možnost použití volby poslat Minidump z hlavního menu - Bitmask - %s - Připojuji se - Čekání na odpověď serveru - Ověřuji autorizaci - Stahuji konfiguraci klienta - Nastavuji IP adresu - Přidávám trasy - Připojeno - Obnovuji připojení - Ukončuji - Neběží - Překlad názvů - Připojuji (TCP) - Přihlášení nebylo úspěšné - Čekání na použitelnou síť - diff --git a/app/src/main/res/values-de/arrays.xml b/app/src/main/res/values-de/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-de/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml deleted file mode 100644 index 84d93cfe..00000000 --- a/app/src/main/res/values-de/strings.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - Abbrechen - Quellcode und Issue Tracker sind verfügbar unter https://github.com/leapcode/bitmask_android/ - Dieses Programm nutzt die folgenden Komponenten. Die kompletten Lizenzdetails sind im Quelltext verfügbar. - Über - Kein Fehler. - Fehler in der Konfiguration - Kann die die IPv4 Adresse nicht parsen - Kann die manuell angegeben Routen nicht parsen - Von der Verknüpfung referenziertes Profil konnte nicht gefunden werden - Route von Android zurückgewiesen. - Trennen - Log löschen. - Trennungsbestätigung - Möchten Sie das VPN trennen bzw. den Verbindungsversuch abbrechen? - Ändere VPN Einstellungen - Auf manchen ROM Version sind eventuell die Zugriffsrechte von /dev/tun falsch oder das tun Kernel Modul fehlt. Für Cyanogenmod 9 ROMs mit root gibt einen provisorischen Fix in den generellen Einstellungen. - Das Öffnen des tun Interfaces ist katastrophal gescheitert - "Fehler: " - Clear - Info - Zeige Verbindungsdetails - Letzte Interface Konfiguration von OpenVPN: - Lokale IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS Server: %s - DNS Domäne: %s - Routen IPv4: %s - Routen IPv6: %s - Interface Information %1$s und %2$s, nehme an, die zweite Adresse ist die Peer Adresse. Benutze /32 Netzmaske für die lokale IP Adresse. Interface Modus spezifiziert von OpenVPN ist \"%3$s\". - Die Route %1$s mit der Netzmaske %2$s ist keine Route mit einer CIDR Netzmaske, benutze /32 als Netzmaske. - Route %1$s/%2$s korrigiert zu %3$s/%2$s - Kann nicht auf die Android Keychain Zertifikate zugreifen (dies kann durch ein System Update oder durch Zurücksichern der Anwendung aus einem Backup hervorgerufen werden). Bitte editieren Sie das VPN und wählen Sie erneut das Zertifikat in dem Grundeinstellungen aus, um die Zugriffsberechtigung für das Zertifikat wieder herzustellen. - %1$s %2$s - Sende Logdatei - Sende - LEAP Android log Datei - Log Eintrag in die Zwischenablage kopiert - Dieses Android ROM enthält keine VPNService API. Sorry :( - Verweigere tun Gerät zu öffnen ohne IP Information - Warte auf OpenVPN Status Nachricht… - Importiertes Profil - Importiertes Profil %d - Der Benutzername darf nicht leer sein - PKCS12 Veschlüsslungspassword - Passphrase privater Schlüssel - Passwort - Generiere OpenVPN Konfiguration… - Zertifikat (KeyStore): \'%s\' - Netzwerkstatus: %s - Beim Abfragen des Android KeyStore wurde kein CA Zertifikat zurückgegeben. Überprüfen des Serverzertifikat wird wahrscheinlich fehlschlagen. Geben Sie manuell ein CA Zertifikat an. - Modell %1$s (%2$s) %3$s, Android API %4$d - Fehler beim Zugriff auf den Android Keystore %1$s: %2$s - Deutsche Übersetzung von Arne Schwabe <arne@rfc2549.org> - Es werden keine DNS Server gesetzt. Möglicherweise wird die DNS Auflösung nicht funktionieren. Ziehen Sie in Betracht die Option \"eigene DNS Server\" zu benutzen. - Konnte den DNS Server \"%1$s\" nicht hinzufügen, da das System ihn zurückweist mit %2$s - Fehler beim Ermitteln der Proxy Einstellungen: %s - Benutzt Proxy %1$s %2$d - Ignorieren - Neu verbinden - Konfigurationsänderungen werden erst nach einem VPN Neustart aktiv. Jetzt neu verbinden? - Konfiguration geändert - Der OpenVPN Prozess ist unerwartet abgestürzt. Bitte erwägen Sie die Option \"Minidump senden\" im Hauptmenü - Bitmask - %s - - Verbinde - Warte auf Serverantwort - Authentifiziere - Warte auf Clientkonfiguration - Weise IP Adressen zu - Hinzufügen von Routen - Verbunden - Wiederverbinden - Beende - Kein OpenVPN Prozess - Löse Hostnamen auf - Verbinde (TCP) - Authentifizierung fehlgeschlagen - Warte auf Internetverbindung - diff --git a/app/src/main/res/values-es/arrays.xml b/app/src/main/res/values-es/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-es/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml deleted file mode 100644 index f8046a3f..00000000 --- a/app/src/main/res/values-es/strings.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - Cancelar - Codigo fuente y sistema de reporte de errores disponibles en https://github.com/leapcode/bitmask_android/ - El programa utiliza los siguientes componentes. Vea los códigos fuentes para obtener más información sobre las licencias - Acerca de - Ruta rechazada por Android - Desconectar - Limpiar registro - Cancelar confirmación - ¿Desconectar la conexión VPN/cancelar el intento de conexión? - Modificar la configuración de VPN - En algunas imágenes personalizadas de ICS los permisos sobre /dev/tun podrían ser incorrectos, o el módulo tun podría faltar completamente. - La apertura de la interfaz tun falĺó - "Error: " - Borrar - información - Mostrar detalles de la conexión - Última configuración de interfaz de OpenVPN: - Local IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - Servidor DNS: %s - Dominio DNS: %s - Rutas: %s - Rutas IPv6: %s - Información de la interfaz obtenida %1$s and %2$s, asumiendo que la segunda dirección es una dirección equivalente del remoto. Usando una máscara de red /32 para la IP local. El modo dado por OpenVPN es \"%3$s\". - No tienen sentido %1$s y %2$s como ruta IP con máscara de red CIDR, usando /32 como máscara de red. - Ruta conectada de %1$s/%2$s a %3$s/%2$s - No se puede acceder a los certificados de Android. Puede ser causado por una actualización de firmware o por restaurar una copia de seguridad de la aplicación/configuración de la aplicación. Por favor edite la VPN y vuelva a seleccionar el certificado bajo meltdownajustes básicos para recrear los permisos para acceder al certificado. - %1$s %2$s - Enviar el archivo de registro - Enviar - Archivo de registro de LEAP de Android - Entrada de registro copiada al Portapapeles - Su imagen no es compatible con la API de VPNService, lo siento :( - Negandose a abrir el dispositivo tun sin información de IP - Esperando el mensaje de estado... - perfil importado - perfil importado %d - El nombre de usuario no debe estar vacío. - Clave PKCS12 de cifrado de archivos - Contraseña de clave privada - Contraseña - Construyendo configuracion... - Conseguido el certificado de \'%s\' de almacén de claves - Estado de la red: %s - No se obtuvo ningún certificado de CA al leer el almacén de claves de Android. La autenticación probablemente fallará. - Ejecutándose en %1$s (%2$s) %3$s, API de Android %4$d - Error al firmar con la llave del almacén de llaves de Android %1$s: %2$s - Traducción al español por José Luis Bandala Perez<luis.449bp@gmail.com> - Sin servidores DNS utilizados. La resolución de nombres puede que no funcione. Considere configurar servidores DNS personalizados - No se puede agregar el servidor DNS \"%1$s\", rechazado por el sistema: %2$s - Error al obtener la configuración de proxy: %s - Usando proxy %1$s %2$d - Ignorar - Reiniciar - Los cambios de configuración se aplican después de reiniciar la VPN. ¿(Re)iniciar la VPN ahora? - Configuración cambiada - - OpenVPN falló inesperadamente. Por favor considere usar la opción envío de minivolcado en el menú principal - diff --git a/app/src/main/res/values-et/arrays.xml b/app/src/main/res/values-et/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-et/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml deleted file mode 100644 index 04dc9572..00000000 --- a/app/src/main/res/values-et/strings.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - Tühista - Lähtetekst ja probleemihaldur asuvad veebilehel https://github.com/leapcode/bitmask_android/ - Programmis kasutatakse järgnevaid komponente. Detailse litsenseerimisinfo leiate lähtekoodist - Lähemalt - Vigu ei leitud - Konfiguratsiooni viga - Sisestatud IPv4 aadress ei allu süntaksianalüüsile - Kohandatud marsruudid ei allu süntaksianalüüsile - Ühendu VPN\'iga - Lühivalikus määratud profiil puudub - Androidi poolt keelatud ruutingud - Katkesta ühendus - Tühjenda logi - Loobu kinnitusest - Katkesta VPN ühendus/tühista ühendumise katse? - Muuda VPN seadistusi - Mõnel modifitseeritud ICS versioonil võivad /dev/tun õigused olla valed, või selle moodul sootuks puududa. - Tun liidese avamine ebaõnnestus - "Viga:" - Tühjenda - info - Näita ühenduse andmeid - Viimane liidese konfigureerimine OpenVPN poolt: - Lokaalne IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS Server: %s - DNS domeen: %s - Marsruudid: %s - IPv6 marsruudid: %s - Liidese andmed on %1$s ja %2$s, eeldades et teine aadress on eemalasuva serveri aadress. Lokaalse IP jaoks kasutatakse /32 võrgumaski. OpenVPN teatab režiimiks %3$s\". - %1$s ja %2$s on mõttetud CIDR võrgumaskiga IP marsruutidest, võrgumaskiks määratakse /32. - %1$s/%2$s marsruut parandatud: %3$s/%2$s - Androidi Keychain sertifikaadid on kättesaamatud. See võib olla põhjustatud püsivara uuendamisest või appide/apiseadistuste taastamisest. Sertifikaatide pääsuõiguste taastamiseks redigeerige palun VPN seadistusi ja valige uuesti üldiste seadistuste alt sertifikaat. - %1$s %2$s - Saada logifail - Saada - LEAP Androidile logifail - Logikirje kopeeriti lõikepuhvrisse - Teie süsteemitarkvara ei toeta VPNService API\'t, vabandame :( - IP andmeteta keeldutakse tun liidese avamisest - Ootan olekuteadet… - imporditud profiil - imporditud profiil %d - Kasutajanimi peab olema määratud. - PKCS12 faili krüpteerimisvõti - Privaatse võtme salasõna - Salasõna - Koostatakse konfiguratsiooni… - Saadud sertifikaat \'%s\' võtmehoidlast - Võrgu olek: %s - Androidi võtmehoidlast lugemine ei andnud ühtegi CA sertifikaati. Suure tõenäosusega autentimine ebaõnnestub. - Töötamas %1$s (%2$s) %3$s peal, Android API %4$d - Viga allkirjastamisel Androidi võtmehoidla võtmega %1$s: %2$s - Eesti keelde tõlkis Robert Tiismus - Ühtegi DNS serverit ei kasutata. Nimelahendus ei pruugi töötada. Vae kohandatud DNS serveri kasutust - DNS serveri \"%1$s\" lisamine ebaõnnestus, süsteemi poolt keelduti: %2$s - Viga proxy seadistuste vastuvõtul: %s - Kasutusel proxy %1$s %2$d - Ignoreeri - Uuestilaadimine - Konfiguratsioonimuudatused rakendatakse peale VPN uuestilaadimist. Kas soovite VPN kohe (uuesti)laadida? - Konfiguratsiooni muudeti - - OpenVPN jooksis ootamatult kokku. Palun kaaluge \"saada Minitõmmis\" valiku lubamist peamenüüs - diff --git a/app/src/main/res/values-fr/arrays.xml b/app/src/main/res/values-fr/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-fr/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml deleted file mode 100644 index 79e4d70c..00000000 --- a/app/src/main/res/values-fr/strings.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - "Annuler" - "Le code source et le tracker de bugs est disponible ici: https://github.com/leapcode/bitmask_android/ " - "Le programme utilise les composants suivants. Voir le code source pour plus de détails sur les licences." - "À propos" - "Aucune erreur" - "Erreur dans la configuration" - "Impossible d\'analyser l\'adresse IPv4" - "Impossible d\'analyser les règles de redirection personnalisés" - "Se connecter au VPN" - "Profil spécifié dans raccourci introuvable" - "Route rejetée par Android" - "Déconnecter" - "Effacer les logs" - "Annuler la confirmation" - "Déconnecter le VPN connecté / annuler la tentative de connexion ?" - "Modifier les paramètres VPN" - "Sur certaines ROMs ICS les permissions de /dev/tun peuvent être incorrectes, ou le module Tun peut être manquant." - "L\'ouverture de l\'interface Tun a échoué." - "Erreur: " - "Effacer" - "infos" - "Afficher les détails de la connexion" - "Dernière configuration connue de l\'interface d\'OpenVPN:" - "IPv4 locale: %1$s/%2$d IPv6: %3$s MTU: %4$d" - "Serveur DNS: %s" - "Domaine DNS: %s" - "Routes: %s" - "Routes IPv6: %s" - "Informations récupérées de l\'interface: %1$s et %2$s , en supposant que la seconde adresse est l\'adresse peer du réseau distant. Utilisation du masque de réseau /32 pour l\'IP locale. Mode donné par OpenVPN: \"%3$s\"." - "Ne peut pas donner un sens à %1$s et %2$s comme routage IP avec masque réseau de type CIDR, en utilisant /32 comme masque de réseau." - "Règle de redirection corrigée: %1$s / %2$s en %3$s / %2$s" - \"Impossible d\'accéder aux certificats \"Android Keychain\". (Peut être causé par une mise à jour du firmware ou par une restauration d\'une sauvegarde des paramètres de l\'application). Veuillez modifier le profil VPN et sélectionnez de nouveau le certificat dans les réglages de base pour recréer l\'autorisation d\'accéder au certificat.\". - "%1$s %2$s" - "Envoyer le fichier de log" - "Envoyer" - "Fichier de log LEAP Android" - "Entrée du log copiée" - "Votre ROM ne prend pas en charge l\'API VPNService, désolé :(" - "Impossible d\'ouvrir le périphérique TUN sans informations IP" - "Attente du status..." - "profil importé" - "profil importé: %d" - "Le nom d\'utilisateur ne doit pas être vide." - "Fichier de clé de cryptage PKCS12" - "Mot de passe de clé privée" - "Mot de passe" - "Création de la configuration ..." - "Certificat \'%s\' obtenu" - "État du réseau: %s" - "Aucun certificat CA renvoyée lors de la lecture depuis le gestionnaire de clés. L\'authentification échouera probablement." - "Fonctionnant sur %1$s (%2$s) %3$s , Android API %4$d" - "Erreur de signature de la clé %1$s : %2$s par le gestionnaire d\'Android" - French translation by Stanislas Bach<stanislasbach@gmail.com> - "Pas de serveurs DNS utilisés. La résolution de noms peut ne pas fonctionner. Envisagez d\'utiliser des serveurs DNS personnalisés." - "Impossible d\'ajouter le serveur DNS \"%1$s\", rejetés par le système: %2$s" - "Erreur d\'obtention des paramètres de proxy: %s" - "Utilisation du proxy %1$s %2$d" - "Ignorer" - "Redémarrer" - "Les changements de configuration sont appliquées après redémarrage du VPN. (Re)démarrer le VPN maintenant?" - "Configuration modifiée" - - diff --git a/app/src/main/res/values-id/arrays.xml b/app/src/main/res/values-id/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-id/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml deleted file mode 100644 index ad893b75..00000000 --- a/app/src/main/res/values-id/strings.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - Batal - Kode program dan perekam masalah tersedia di - Aplikasi memakai komponen berikut; lihat kode program untuk lebih jelas mengenai lisensi - Tentang... - Tidak ada kesalahan - Gagal menganalisa rute buatan - Hubungkan VPN - Profil di shrotcut tidak ada - Rute ditolak Android - Putus - Bersihkan catatan - Batal Konfirmasi - Putuskan sambungan VPN/Batalkan usaha menyambungkan VPN? - Ubah seting OpenVPN - Pada beberapa setelan manual gambar ICS izin pada/dev/tun mungkin salah, atau modul tun mungkin hilang sepenuhnya. - Gagal membuka layanan antarmuka TUN - "Kesalahan: " - Bersihkan - Info - Detail koneksi - Konfigurasi terakhir dari OpenVPN: - IPv4 lokal : %1$s/%2$d IPv6: %3$s MTU: %4$d - Server DNS : %s - Domain DNS : %s - Rute: %s - Rute IPv6: %s - Memilki informasi antarmuka %1$ s dan %2$, asumsi alamat kedua adalah alamat remote. Menggunakan netmask /32 untuk IP lokal. Mode yang diberikan oleh OpenVPN adalah \"%3$\". - Tidak masuk akal membuat %1$ s dan %2$ s sebagai rute IP dengan netmask CIDR, Gunakan /32 sebagai netmask. - rute yang diperbaiki %1$s/%2$s hingga %3$s/%2$s - Tidak dapat mengakses sertifikat Keychain Android. Dapat disebabkan karena upgrade firmware atau pengembalian backup pengaturan app. Mohon ubah VPN, dan pilih ulang sertifikat berbasis pengaturan dasar agar izin mengakses sertifikat dapat dibuat ulang. - %1$s %2$s - Kirim berkas catatan - Kirim - Berkas catatan LEAP Android - Salin catatan masuk ke clipboard - Gambar Anda tidak mendukung VPNService API, maaf:( - TUN tidak dapat dibuka tanpa informasi IP - Menunggu pesan status… - Profil yang diambil - profil yang diambil %d - Nama pengguna tidak boleh kosong. - Berkas kunci enkripsi PKCS12 - Sandi kunci pribadi - Sandi - Membuat konfigurasi… - Sertifikat didapatkan \'%s\' dari Keystore - Status jaringan: %s - Tidak ada sertifikat CA yang didapat saat membaca dari Android Keystore. Otentifikasi mungkin gagal - Berjalan di %1$s (%2$s) %3$s, Android API %4$d - Kesalahan masuk dengan kunci Android keystore %1$ s: %2$ s - Terjemah Bahasa Indonesia oleh Dayro - Tidak ada server DNS yang digunakan. Resolusi nama mungkin tidak bisa bekerja. Pertimbangkan seting server DNS yang diatur sendiri - Tak bisa menambahkan Server DNS \"%1$ s\", ditolak oleh sistem: %2$ s - Gagal mendapatkan pengaturan proxy: %s - Menggunakan proxy %1$ s %2$ d - Abaikan - Restart - Perubahan konfigurasi baru diterapkan setelah restart VPN. Restart VPN sekarang? - Konfigurasi berubah - - OpenVPN crash tak terduga. Silakan mempertimbangkan mengirim menggunakan pilihan Minidump di Menu Utama - Bitmask - %s - - Menghubungkan - Menunggu jawaban server - Melakukan otentifikasi - Mengambil konfigurasi klien - Menetapkan alamat IP - Menambahkan rute - Terhubung - Menghubungkan kembali - Keluar - Tidak berjalan - Mengenali nama host - Menghubungkan (TCP) - Otentifikasi gagal - Menunggu jaringan yang dapat dipakai - diff --git a/app/src/main/res/values-it/arrays.xml b/app/src/main/res/values-it/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-it/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml deleted file mode 100644 index 788dbcc5..00000000 --- a/app/src/main/res/values-it/strings.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - Annulla - Il codice sorgente e il bug tracker sono disponibili all\'indirizzo https://github.com/leapcode/bitmask_android/ - Questo programma usa i seguenti componenti; guarda il codice sorgente per i dettagli completi sulle licenze - Informazioni - Nessun errore trovato - Errore nella configurazione - Impossibile analizzare l\'indirizzo IPv4 - Errore durante la lettura delle regole di reindirizzamento (routing) - Connetti alla VPN - Profilo indicato nel collegamento non trovato - Reindirizzamento (route) rifiutato da Android - Scollega - Cancella registro - Conferma l\'annullamento - Disconnetti la VPN in uso/annulli il tentativo di connessione? - Modifica impostazioni VPN - In alcune immagini ICS personalizzate i permessi su /dev/tun potrebbero essere errati, oppure il modulo TUN completamente assente. - Impossibile accedere all\'interfaccia tun - "Errore:" - Azzera - Info - Visualizza i dettagli della connessione - Ultima configurazione interfaccia OpenVPN: - Indirizzi locali - IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - Server DNS: %s - Dominio DNS: %s - Instradamenti (route): %s - Instradamenti (route) IPv6: %s - Ottenute le informazioni sulle interfacce %1$s e %2$s, assumendo che il secondo indirizzo sia il peer remoto. Utilizzata la maschera /32 per l\'IP locale. La modalità impostata da OpenVPN è \"%3$s\". - Impossibile utilizzare %1$s e %2$s come reindirizzamenti IP con la maschera CIDR, è stata quindi usata la maschera /32. - Instradamento %1$s/%2$s corretto con %3$s/%2$s - Impossibile accedere ai certificati della Keychain di Android (può essere causato da un aggiornamento del firmware o dal ripristino di un backup dell\'applicazione o delle sue impostazioni). E\' necessario modificare le impostazioni della VPN e riselezionare il certificato nelle impostazioni principali per ricreare i permessi di accesso al certificato. - %1$s %2$s - Invia il file di log - Invia - File log di LEAP Android - Voce di registro copiata negli appunti - La tua immagine non è supportata dal VPNService API, mi dispiace :( - Rifiuto di attivare il dispositivo tun senza informazioni sull\'IP - In attesa del messaggio di stato... - profilo importato - profilo importato %d - L\'username non deve essere vuoto. - File con la chiave di crittografia PKCS12 - Password della chiave privata - Password - Configurazione in corso... - Ottenuto il certificato \'%s\' dal Keystore - Stato della rete: %s - Nessun certificato della CA è stato prelevato dal Keystore di Android. E\' probabile che l\'autenticazione fallisca. - In esecuzione su %1$s (%2$s) %3$s, Android API %4$d - Errore di firma con la chiave %1$s: %2$s del Keystore di Android. - Traduzione in inglese di Arne Schwabe<arne@rfc2549.org> - Nessun server DNS in uso. La risoluzione dei nomi potrebbe non funzionare. Valuta se inserire dei server DNS personalizzati. - Impossibile aggiungere il server DNS \"%1$s\", respinto dal sistema: %2$s - Errore nell\'ottenere le impostazioni del proxy: %s - Si sta utilizzando il proxy %1$s %2$d - Ignora - Riavvia - Le modifiche sarannoi applicate dopo aver riavviato la connessione VPN. Riavviare ora la connessione? - Configurazione modificata - - OpenVPN si è arrestato in modo imprevisto. Ti consigliamo di attivare l\'opzione Invia Minidump nel menu principale. - diff --git a/app/src/main/res/values-ja/arrays.xml b/app/src/main/res/values-ja/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-ja/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml deleted file mode 100644 index 4503c227..00000000 --- a/app/src/main/res/values-ja/strings.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - キャンセル - ソースコードと問題管理は以下で: https://github.com/leapcode/bitmask_android/ - プログラムは、次のコンポーネントを使用します。完全な詳細についてはソース上のライセンスを参照してください。 - バージョン情報 - エラーは見つかりませんでした。 - 設定に誤りがあります。 - IPv4 アドレスの解析エラー - カスタム経路の解析エラー - VPNに接続 - ショートカットで指定されたプロファイルが見つかりません - 経路がAndroidにより拒否されました。 - 切断 - ログをクリア - キャンセルの確認 - 接続中または試行中の接続をキャンセルしますか? - VPN 設定の編集 - いくつかのカスタムICSイメージは、/dev/tunのパーミッションが異常か、TUNモジュールが含まれていません。 - TUNデバイスを開こうとして失敗しました。 - "エラー:" - クリア - 情報 - 接続の詳細を表示 - OpenVPNから設定した最後のインターフェイス: - ローカル IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS サーバー: %s - DNS ドメイン: %s - 経路:%s - 経路 IPv6:%s - インターフェース情報として[%1$s]と[%2$s]を取得しました。2つめのアドレスはリモート側のピアアドレスです。32ビットマスクをローカルIPに使用します。 OpenVPNのモードは[%3$s]です。 - %1$sと%2$sではCIDR形式のIP経路情報として意味をなしません。32ビットマスクを使用します。 - 経路情報%1$s/%2$sを%3$s/%2$sに修正しました。 - Androidの証明書管理にアクセスできません。(ファームウェアの更新、アプリケーションまたはその設定のリストアによって発生する場合があります)。VPNの設定で証明書の選択を再度行ってください。 - %1$s %2$s - ログ ファイルを送信します。 - 送信 - LEAP Android ログ ファイル - クリップ ボードにコピーされたログ エントリ - 申し訳ありませんが、お使いの環境ではVPNサービスがサポートされていません。 - IP情報なしでのTUNデバイス使用は拒否しています - 状態メッセージを待っています。 - インポートされたプロファイル - インポートされたプロファイル %d - ユーザ名を空に設定することはできません - PKCS12ファイルの暗号化キー - 秘密鍵のパスワード - パスワード - 構成中・・・ - \'%s\'の証明書をキーストアから読み出し - ネットワーク状態: %s - 認証局証明書(CA Cert)がAndroidのキーストアから取得できませんでした。認証はおそらく失敗します。 - 実行中:%1$s (%2$s)%3$s Android API %4$d - Androidキーストアに保存されたキー %1$s: %2$sの署名エラーです - 日本語翻訳 高橋正希@埼玉 <tools@artin.nu> - 使用されている DNS サーバーはありません。名前解決は動作しません。DNSサーバーの設定を見直してください。 - DNSサーバ \"%1$s\" の追加に失敗しました。%2$sに拒否されました。 - プロキシ設定でエラー: %s - プロキシを使用します %1$s %2$d - 無視 - 再起動 - 設定の変更はVPNの再起動後に反映されます。VPNを(再)起動しますか? - 設定が変更されました - - OpenVPN は予期せず終了しました。メイン メニューでミニダンプの送信オプションを検討してください。 - Bitmask - %s - - 接続中 - サーバーの応答を待っています。 - 認証中 - クライアントの構成を取得中 - IPアドレスを割り当て中 - 経路を追加中 - 接続しました - 再接続中 - 終了中 - 停止中 - ホスト名を解決中 - 接続中(TCP) - 認証に失敗しました - 使用可能なネットワークを待機中 - diff --git a/app/src/main/res/values-ko/arrays.xml b/app/src/main/res/values-ko/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-ko/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml deleted file mode 100644 index 8a725bad..00000000 --- a/app/src/main/res/values-ko/strings.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 소스 코드와 문제 추적기는 https://github.com/leapcode/bitmask_android/에서 사용할 수 있습니다 - 프로그램은 다음 구성 요소를 사용합니다. 라이선스에 대 한 자세한 내용은 소스를 참조 하십시오 - 소개 - 오류 없음 - 설정 오류 - IPv4 주소 구문 분석 오류 - 사용자 지정 경로 구문 분석 오류 - VPN에 연결 하기 - 바로 가기에 지정 된 프로파일을 찾을 수 없습니다. - 안드로이드에 의해 거부된 라우트 - 연결 끊기 - 로그 지우기 - 취소 확인 - 연결된 VPN 끊기/연결시도 취소? - VPN 설정 편집 - 일부 사용자 지정 ICS 이미지에서는 /dev/tun에 대한 권한이 잘못 되어 있거나 tun 모듈 자체가 누락 될 수 있습니다. - Tun 인터페이스를 열지 못했습니다 - "오류:" - 지우기 - 정보 - 연결 세부 정보 보기 - Openvpn에서 마지막 인터페이스 구성: - 로컬 IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS 서버: %s - DNS 도매인: %s - 라우트: %s - 라우트 IPv6: %s - 인터페이스 정보 %1$s와 %2$s에 있어, 두 번째 주소를 원격 피어 주소로 가정 하겠습니다. 로컬 IP의 넷마스크로는 /32를 사용하겠습니다. OpenVPN에 의해 주어진 모드는 \"%3$s\" 입니다. - CIDR 넷마스크가 있는 IP 라우트 %1$s 와 %2$s 에 있어서 처리가 불가능합니다. /32를 넷마스크로 사용하겠습니다. - %1$s/%2$s 에서 %3$s/%2$s로 라우트 수정 - 안드로이드 키체인 인증서에 접근할 수 없습니다. 펌웨어 업그래이드 또는 백업된 앱/앱 설정을 복구하면서 발생할 수 있습니다. 인증서에 액세스할 수 있는 권한을 다시 생성하기 위해 VPN을 편집 하고 기본 설정 아래에서 인증서를 다시 선택 하십시오. - %1$s %2$s - 로그 파일 보내기 - 보내기 - LEAP 로그 파일 - 클립보드로 로그 복사 - 당신의 이미지는 VPNService API를 지원 하지 않습니다, 죄송 합니다:( - IP 정보가 없는 tun 장치 열기를 거부합니다 - 상태 메시지를 기다리는 중... - 가져온 프로파일 - 가져온 프로파일 %d - 사용자 이름이 비어 있지 않아야 합니다. - PKCS12 파일 암호화 키 - 개인 키 암호 - 암호 - 설정 만드는중… - Keystore에서 인증서 \'%s\' 받음 - 네트워크 상태: %s - 안드로이드 keystore에서 CA 인증서를 찾지 못했습니다. Auhtentication은 실패할 것 입니다. - %1$s (%2$s) %3$s, 안드로이드 API %4$d 에서 실행 - 안드로이드 keystore 키 %1$s: %2$s과 싸이닝 오류 - 한국어 번역 (주)기가드 안규태<ktdann@gmail.com> - 사용 중인 DNS 서버가 없습니다. 이름 확인이 작동 하지 않을 수 있습니다. 사용자 지정 DNS 서버의 사용을 고려해 보세요. - DNS서버 \"%1$s\" 는, 시스템에 의해 거부되 추가할 수 없습니다: %2$s - 프록시 설정 가져오기 오류: %s - 프록시 %1$s %2$d 을 사용 - 무시 - 다시 시작 - VPN을 다시 시작한 후 설정 변경 내용이 적용 됩니다. VPN을 지금 (재)시작? - 설정 변경 - - OpenVPN이 예기치 않게 종료됐습니다. 메인 메뉴에 있는 미니 덤프 보내기 옵션의 사용을 고려 하시기 바랍니다 - diff --git a/app/src/main/res/values-nl/arrays.xml b/app/src/main/res/values-nl/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-nl/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml deleted file mode 100644 index 0442b054..00000000 --- a/app/src/main/res/values-nl/strings.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - Over - Geen fout. - Fout in de configuratie - Met VPN verbinden - Route geweigert door Android - Verbinding verbreken - logboek wissen - Annuleer bevestiging - Sluit de verbonden VPN af/annuleer de verbindingspoging? - VPN Instellingen Bewerken - "Fout:" - Leeg maken - info - Details van de verbinding weergeven - Laatste interfaceconfiguratie van OpenVPN: - Lokaal IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS Server: %s - DNS Domein: %s - Routes: %s - Routes IPv6: %s - %1$s %2$s - Logboek verzenden - Verzenden - Wachten op status bericht… - Geïmporteerd profiel - Geïmporteerd profiel %d - De gebruikersnaam moet niet leeg zijn. - PKCS12 Bestand Encryptie Sleutel - Privé Sleutel Wachtwoord - Wachtwoord - diff --git a/app/src/main/res/values-no/arrays.xml b/app/src/main/res/values-no/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-no/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml deleted file mode 100644 index a363b2f9..00000000 --- a/app/src/main/res/values-no/strings.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - Om - Ingen feil funnet - Feil i konfigurasjonen - Koble til VPN - Koble fra - Tøm logg - Avbryt bekreftelse - Rediger VPN-innstillinger - "Feil:" - Fjern - info - Vis Tilkoblingsdetaljer - DNS-server: %s - DNS-domene: %s - Ruter: %s - %1$s %2$s - Send loggfilen - Send - LEAP Android loggfil - Venter på tilstands melding... - importert profil - importert profilen %d - Brukernavnet kan ikke være tomt. - PKCS12 Filkrypteringsnøkkel - Privat nøkkel passord - Passord - Lager konfigurasjon... - Nettverksstatus: %s - Kjører på %1$s (%2$s) %3$s, Android API %4$d - Norsk oversettelse av Jonny - Feil ved henting av proxy-innstillinger: %s - Bruker proxy %1$s %2$d - Ignorer - Start på nytt - Konfigurasjon endret - diff --git a/app/src/main/res/values-ro/arrays.xml b/app/src/main/res/values-ro/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-ro/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml deleted file mode 100644 index 22496320..00000000 --- a/app/src/main/res/values-ro/strings.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - Cod sursă şi tracker probleme disponibile la https://github.com/leapcode/bitmask_android/ - Acest program utilizează următoarele componente; a se vedea codul sursă pentru mai multe detalii despre licente - Despre - Nu s-a găsit nici o eroare - Eroare în configurare - Eroare parsare adresă IPv4 - Eroare parsare rute particularizate - Conectare la VPN - Profilul specificat în comanda rapidă nu a fost găsit - Rută respinsă de Android - Deconectaţi - Golire jurnal - Anulare confirmare - Deconectaţi VPN-ul conectat/anulaţi încercarea de conectare? - Editaţi setările VPN - În cazul anumitor imagini particularizate de ICS permisiile pentru /dev/tun pot fi greşite sau modulul tun lipseşte cu desăvârşire. - Eroare deschidere interfaţa tun - "Eroare:" - Goleşte - info - Arată detaliile conexiunii - Ultima configurare interfaţă din OpenVPN: - Local IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - Server DNS: %s - Domeniu DNS: %s - Rute: %s - Rute IPv6: %s - S-au primit informaţiile despre interfaţă %1$s şi %2$s, presupun că a doua adresă este adresa peer a serverlui remote. Folosesc netmask /32 pentru IP local. Modul dat de OpenVPN este \"%3$s\". - %1$s %2$s - Trimite fişier jurnal - Trimite - Fişier jurnal LEAP Android - profil importat - profil importat %d - Cheie criptare fişier PKCS12 - Parola cheie privată - Parola - Se generează configurarea… - Am primit certificatul \'%s\' din Keystore - Statutus reţea: %s - Rulează pe %1$s (%2$s) %3$s, Android API %4$d - Eroare semnare cu Android keystore key %1$s: %2$s - Folosesc proxy %1$ s %2$ d - Ignora - Restart - Configuraţie schimbată - - OpenVPN sa oprit în mod neaşteptat. Vă rugăm să consideraţi opţiunea de trimitere a unui Minidump din meniul principal - diff --git a/app/src/main/res/values-ru/arrays.xml b/app/src/main/res/values-ru/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-ru/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml deleted file mode 100644 index aaa42690..00000000 --- a/app/src/main/res/values-ru/strings.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - Данная программа использует следующие компоненты; смотрите исходный код для получения подробной информации о лицензии - О программе - Ошибок не найдено - Ошибка в конфигурации - Невозможно прочесть IPv4 адрес - Невозможно примениить пользовательские маршруты - Подключиться к VPN - Не найден профиль, указанный в ярлыке - Маршрут отвергнут Android - Отключение - очистить журнал - Подтверждение отмены - Отключение активных VPN/Отмена попыток подключения? - Редактирование параметров VPN - На некторых костомных сборках права на /dev/tun могут быть неверными или tun-модуль может быть не включен. Для прошивки CM9 можете попробовать исправить владельца прямо из настроек программы - Открытие интерфейса tun окончилось неудачей - "Ошибка: " - Очистить - информация - Показать подробности о подключении - Последняя используемая конфигурация OpenVPN: - Адрес IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS-сервер: %s - DNS-домен: %s - Маршруты: %s - Маршруты IPv6: %s - Получена информация интерфейса %1$s и %2$s, второй адрес является удалённым адресом канала. Используется сетевая маска /32 для локального IP адреса. Режим, установленный OpenVPN: \"%3$s\". - Невозможно использовать выражения %1$s и %2$s как маршрут по стандарту CIDR. используется /32 как маска подсети. - Маршрут исправлен с %1$s/%2$s на %3$s/%2$s - Не удается получить доступ к хранилищу ключей и сертификатов Android. Это может быть вызвано обновлением прошивки или восстановления старой копии приложения или его настроек. Пожалуйста, отредактируйте профиль VPN и заново укажите ключи и сертификаты в разделе Основные параметры. - %1$s %2$s - Отправить файл журнала - Отправить - LEAP Android файла лога - Скопировать лог в буфер обмена - Ваша прошивка не поддерживает VPNService API, извините :( - Отказ в открытии устройства tun без информации об IP-адресе - Ожидание сообщения о состоянии… - импортируемый профиль - импортируемый профиль %d - Имя пользователя не должно быть пустым. - Файл PKCS12-ключа - Пароль закрытого ключа - Пароль - Создание конфигурации… - Получен сертификат \'%s\' из хранилища ключей - Статус сети: %s - Не удалось получить CA из хранилища ключей Android. Аутентификация не удалась. - Работает на %1$s (%2$s) %3$s, Android API %4$d - Ошибка подписи с использованием ключа из хранилища Android %1$s: %2$s - Русский перевод от RusFox <horonitel@gmail.com> - DNS-серверы не используются. Разрешение имен может не работать. Рассмотрите возможность указания DNS-серверов - Не удалось добавить DNS-сервер \"%1$s\", отклонен системой: %2$s - Ошибка при получении параметров прокси-сервера: %s - Используется прокси-сервер %1$s %2$d - Игнорировать - Перезагрузка - Изменения конфигурации применяются после перезапуска VPN. (Пере)запустить VPN теперь? - Конфигурация изменена - - OpenVPN завершилась неожиданно. Пожалуйста, посмотрите опцию \"Отправить Minidump\" в главном меню - Bitmask - %s - - Подключение - Ожидание ответа сервера - Проверка подлинности - Получение конфигурации клиента - Назначение IP-адресов - Добавление маршрутов - Подключено - Повторное подключение - Выход - Не запущено - Разрешение имен узлов - Подключение (TCP) - Ошибка аутентификации - Ожидание работы сети - diff --git a/app/src/main/res/values-uk/arrays.xml b/app/src/main/res/values-uk/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-uk/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml deleted file mode 100644 index dab28b48..00000000 --- a/app/src/main/res/values-uk/strings.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - Початковий код і відстеження проблем доступні по https://github.com/leapcode/bitmask_android/ - Ця програма використовує такі компоненти; перегляньте вихідний код для повної інформації про ліцензії - Про - Помилок не знайдено - Помилка конфігурації - Помилка при розборі адреси IPv4 - Помилка аналізу налаштованих маршрутів - Підключення до VPN - Профіль, вказаний у ярлику, не знайдено - Маршрут відхилено Андроїдом - Від\'єднати - очистити журнал - Підтвердження скасування - Відключення активних VPN/скасувати спробу підключення? - Змінити налаштування VPN - На деяких користувацьких прошивках ICS права на /dev/tun можуть бути невірними або модуль tun може бути взагалі відсутнім. - Не вдалося відкрити інтерфейс tun - "Помилка: " - Очистити - Інформація - Показати відомості про підключення - Останній інтерфейс конфігурації з OpenVPN: - Адреса IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS-сервер: %s - DNS домен: %s - Маршрути: %s - Маршрути IPv6: %s - Отримано інформацію інтерфейсу %1$s і %2$s, друга адреса є віддаленою адресою каналу. Використовується мережева маска /32 для локальної IP-адреси. Режим, встановлений OpenVPN: \"%3$s\". - Неможливо використовувати вирази %1$s і %2$s як маршрут за стандартом CIDR. використовується /32 як маска підмережі. - Виправлено маршрут з %1$s/%2$s на %3$s/%2$s - Не можна отримати доступ до сховища ключів та сертифікатів Андроїд. Це можливо спричинено оновлення прошивки або відновленням резервної копії програми чи її налаштувань. Будь ласка, відредагуйте профіль VPN та заново виберіть сертифікат у основних параметрах для створення доступу до сертифікату. - %1$s %2$s - Надіслати файл журналу - Надіслати - LEAP Android файл журналу - Скопійовано запис журналу до буферу обміну - Ваша прошивка не підтримує VPNService API, вибачте :( - Відмова у відкритті пристрою tun без інформації про IP-адресу - Очікування повідомлення стану... - імпортований профіль - імпортований профіль: %d - Ім\'я користувача не може бути порожнім. - PKCS12 Ключ шифрування файлу - Пароль закритого ключа - Пароль - Побудова конфігурації… - Отримано сертифікат \'%s\' з сховища ключів - Статус мережі: %s - Не вдалося отримати СА сертифікат при читанні із сховища ключів Андроїд. Автентифікація не вдалася. - Працює на %1$s (%2$s) %3$s, Android API %4$d - Помилка підпису з використанням ключа із сховища Андроїд %1$s: %2$s - Переклад українською від wvolov - Жодний DNS сервер не використовується. Розширення імен можуть не працювати. Розгляньте можливість вказання DNS серверів - Не можливо додати DNS-сервер \"%1$s\", відхилено системою: %2$s - Помилка отримання параметрів проксі: %s - Використовується проксі %1$s %2$d - Ігнорувати - Перезапустити - Після перезапуску VPN застосувати зміни конфігурації. (Пере)запустити VPN тепер? - Конфігурація змінена - - OpenVPN впав несподівано. Будь ласка, розгляньте використання параметру \"Надіслати Мінідамп\" в головному меню - Bitmask - %s - - Підключення - Очікування відповіді сервера - Аутентифікація - Отримання конфігурації клієнта - Перепризначення IP-адрес - Додавання маршрутів - Підключено - Повторне підключення - Виходимо - Не працює - Розпізнавання імен вузлів - Підключення (TCP) - Помилка автентифікації - Очікування на використання мережі - diff --git a/app/src/main/res/values-zh-rCN/arrays.xml b/app/src/main/res/values-zh-rCN/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-zh-rCN/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml deleted file mode 100644 index ae57d277..00000000 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - 请前往 https://github.com/leapcode/bitmask_android/ 源码或提供问题反馈 - 本程序使用以下组件,请在 Licenses 查看源码获取更详细内容。 - 关于 - 未找到错误 - 配置有错误 - 无法解析 IPv4 地址 - 无法解析自定义路由 - 连接到 VPN - 未找到快捷方式中指定的配置文件 - Android 拒绝了路由 - 断开 - 清除日志 - 取消确认 - 断开已连接的 VPN / 取消连接尝试? - 编辑 VPN 设置 - 未能打开 tun 模块 - 错误 - 清除 - 信息 - 显示连接信息 - 最后 OpenVPN 接口配置: - 本地 IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS 服务器: %s - DNS 域: %s - IPv4 路由: %s - IPv6 路由: %s - %1$s %2$s - 发送日志文件 - 发送 - LEAP Android 日志文件 - 日志条目已复制剪贴板 - 无 IP 信息,拒绝打开 tun 设备 - 等待状态消息 - 已导入配置文件 - 已导入配置文件 %d - 用户名不能为空。 - PKCS12 文件加密密钥 - 私钥密码 - 密码 - 正在生成配置 - 网络状态: %s - 运行 %1$s ( %2$s ) %3$s ,Android API %4$d - 中文翻译: 白达卫 -<59539051+ovpntrans.zh@mail.dcu.ie> - 获取代理设置时出错:%s - 使用代理 %1$s %2$d - 忽略 - 重启 - 配置已更改 - diff --git a/app/src/main/res/values-zh-rTW/arrays.xml b/app/src/main/res/values-zh-rTW/arrays.xml deleted file mode 100644 index 045e125f..00000000 --- a/app/src/main/res/values-zh-rTW/arrays.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml deleted file mode 100644 index 880fb4ad..00000000 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - 取得原始碼與個案追蹤,可上 https://github.com/leapcode/bitmask_android/ - 本程序使用了以下元件,其作者和授權資訊如下 - 關於 - 未有找到錯誤 - 設定中含有錯誤 - 解析IPv4地址時發生錯誤 - 解析自訂路由時發生錯誤 - 連接到VPN - 在快捷方式找不到指定的設定檔 - 路由被Android拒絕 - 斷線 - 清除記錄檔 - 確認取消 - 編輯VPN設定 - 一些自訂的Android4.0 ROM存在/dev/tun的擁有者權限問題,甚至完全沒有Tun模組。CM9用家請嘗試於\"全域設置\"下修正Tun擁有者。 - 無法開啟Tun網絡介面 - "錯誤: " - 清除 - 資訊 - 顯示連線的詳細資訊 - 本地IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS伺服器: %s - DNS網域: %s - 路徑: %s - IPv6路由: %s - %1$s %2$s - 分享記錄檔 - 分享 - LEAP Android 記錄檔 - 已將記錄複製到剪貼簿 - 你的Android ROM不支援VPN服務API,抱歉了。 :( - 等待狀態訊息... - 使用者名稱不能為空。 - PKCS12檔加密金鑰 - 私密金鑰密碼 - 密碼 - 正在生成設定檔… - 網絡狀態: %s - 於 %1$s (%2$s) %3$s 上運行, Android API 版本: %4$d - 繁體中文 由 羊羊@自由網絡研究中心 <sora8964@gmail.com> 翻譯 - 沒有任何DNS伺服器可用,可能無法進行網域名稱解析。請考慮設置自訂的DNS伺服器 - 取得代理伺服器資訊時發生錯誤: %s - 使用代理伺服器 %1$s %2$d - 忽略 - 重置 - 配置變更只會在重新啟動VPN時才生效,現在要(重新)啟動VPN嗎? - 設定已變更 - - OpenVPN非預期地崩潰,你或者會考慮在主選單下傳送Minidump給開發人員。 - diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml deleted file mode 100644 index 66f07a87..00000000 --- a/app/src/main/res/values/arrays.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml deleted file mode 100644 index cb503b86..00000000 --- a/app/src/main/res/values/attrs.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index bcb6ecc9..00000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,143 +0,0 @@ - - - - Cancel - OK - Retry - Source code and issue tracker available at https://github.com/leapcode/bitmask_android/ - Translations welcome and appreciated. See our Transifex project at https://www.transifex.com/projects/p/bitmask-android/ - This program uses the following components; see the source code for full details on the licenses - About Bitmask - Switch provider - No error found - Error in Configuration - Error parsing the IPv4 address - Error parsing the custom routes - Connect to VPN - Profile specified in shortcut not found - Route rejected by Android - Disconnect - clear log - Cancel Confirmation - Disconnect the connected VPN/cancel the connection attempt? - Edit VPN Settings - On some custom ICS images the permission on /dev/tun might be wrong, or the tun module might be missing completely. - Failed to open the tun interface - "Error: " - Clear - info - Show connection details - Last interface configuration from OpenVPN: - Local IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d - DNS Server: %s - DNS Domain: %s - Routes: %s - Routes IPv6: %s - Got interface information %1$s and %2$s, assuming second address is peer address of remote. Using /32 netmask for local IP. Mode given by OpenVPN is \"%3$s\". - Cannot make sense of %1$s and %2$s as IP route with CIDR netmask, using /32 as netmask. - Corrected route %1$s/%2$s to %3$s/%2$s - Cannot access the Android Keychain Certificates. This can be caused by a firmware upgrade or by restoring a backup of the app/app settings. Please edit the VPN and reselect the certificate under basic settings to recreate the permission to access the certificate. - %1$s %2$s - Send log file - Send - Bitmask OpenVPN log file - Copied log entry to clip board - Your image does not support the VPNService API, sorry :( - Refusing to open tun device without IP information - Waiting for state message… - imported profile - imported profile %d - The username must not be empty. - PKCS12 File Encryption Key - Private Key Password - Password - Building configuration… - Got certificate \'%s\' from Keystore - Network Status: %s - No CA Certificate returned while reading from Android keystore. Authentication will probably fail. - Running on %1$s (%2$s) %3$s, Android API %4$d - Error signing with Android keystore key %1$s: %2$s - English translation by Arne Schwabe <arne@rfc2549.org> - No DNS servers being used. Name resolution may not work. Consider setting custom DNS Servers - Could not add DNS Server \"%1$s\", rejected by the system: %2$s - Error getting proxy settings: %s - Using proxy %1$s %2$d - Ignore - Restart - Configuration changes are applied after restarting the VPN. (Re)start the VPN now? - Configuration changed - OpenVPN crashed unexpectedly. Please consider using the send Minidump option in the main menu - Bitmask - %s - Connecting - Waiting for server reply - Authenticating - Getting client configuration - Assigning IP addresses - Adding routes - Connected - Reconnecting - Exiting - Not running - Resolving host names - Connecting (TCP) - Authentication failed - Waiting for usable network - Settings - Bitmask - Provider: - No provider configured - Access EIP connection settings - Status unknown. - Connection will be secure using an anonymous certificate. - Connection secure using an anonymous certificate. - Connection will be secure using your own certificate. - Connection secure using your own certificate. - Encrypted Internet - Select a service provider - Add new Provider - Add a new service provider - Save - Domain name - It seems your URL is well formed - It seems your URL is not well formed - Provider details - Use anonymously - username - Please enter your username - password - User message - About Bitmask" - Try again: server math error. - Incorrect username or password. - It should have at least 8 characters. - Try again: Client HTTP error - Try again: I/O error - Try again: Bad response from the server - Update the app - Log In - Log Out - Skip security check - Configuration Error - Configure - Exit - There was an error configuring Bitmask with your chosen provider.\n\nYou may choose to reconfigure, or exit and configure a provider upon next launch. - Server is unreachable, please try again. - It doesn\'t seem to be a Bitmask provider. - This is not a trusted Bitmask provider. - Configuring provider - Your anon cert was not downloaded - Logging in - Logging out from this session. - Didn\'t logged out. - Authentication succeeded. - Authentication failed. - Your own cert has been correctly downloaded. - Your own cert has incorrectly been downloaded. - Initiating connection - Cancel connection? - There is a connection attempt in progress. Do you wish to cancel it? - Yes - No - "Not running! Connection not secure!" - Connection Secure. - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml deleted file mode 100644 index 1c20cbcd..00000000 --- a/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/untranslatable.xml b/app/src/main/res/values/untranslatable.xml deleted file mode 100644 index 6435bfbf..00000000 --- a/app/src/main/res/values/untranslatable.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - Bitmask - Bitmask - - Copyright 2012\nLEAP Encryption Access Project <info@leap.se> - Copyright © 2002–2010 OpenVPN Technologies, Inc. <sales@openvpn.net>\n - - "OpenVPN" is a trademark of OpenVPN Technologies, Inc. - Copyright © 1996 – 2011 Markus Franz Xaver Johannes Oberhumer - This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit\n - Copyright © 1998-2008 The OpenSSL Project. All rights reserved.\n\n - This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)\n - Copyright © 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved. - OpenVPN - LZO - OpenSSL - Unknown state - -- cgit v1.2.3 From 1684c8f398922065a97e7da4dac4ac6a33cc5218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Wed, 9 Apr 2014 16:03:55 +0200 Subject: Back to the standard "app" module. This return to "app" instead of "bitmask_android" is due to this reading: https://developer.android.com/sdk/installing/studio-build.html#projectStructure I'll have to tweak the final apk name in build.gradle. --- .../leap/bitmaskclient/test/ConnectionManager.java | 33 + .../test/testConfigurationWizard.java | 65 ++ .../se/leap/bitmaskclient/test/testDashboard.java | 94 +++ .../bitmaskclient/test/testLeapSRPSession.java | 636 +++++++++++++++ app/src/debug/AndroidManifest.xml | 27 + app/src/debug/assets/urls/cdev.bitmask.net.url | 3 + app/src/debug/assets/urls/dev.bitmask.net.url | 3 + .../se/leap/bitmaskclient/ConfigurationWizard.java | 581 ++++++++++++++ .../se/leap/bitmaskclient/NewProviderDialog.java | 123 +++ .../java/se/leap/bitmaskclient/ProviderAPI.java | 875 +++++++++++++++++++++ .../leap/bitmaskclient/ProviderDetailFragment.java | 115 +++ .../se/leap/bitmaskclient/ProviderListContent.java | 112 +++ .../res/layout-xlarge/new_provider_dialog.xml | 26 + app/src/debug/res/layout/new_provider_dialog.xml | 24 + app/src/debug/res/values/strings.xml | 4 + app/src/main/AndroidManifest.xml | 82 ++ app/src/main/assets/minivpn.armeabi | Bin 0 -> 5216 bytes app/src/main/assets/minivpn.armeabi-v7a | Bin 0 -> 5228 bytes app/src/main/assets/minivpn.mips | Bin 0 -> 5164 bytes app/src/main/assets/minivpn.x86 | Bin 0 -> 5268 bytes app/src/main/assets/urls/bitmask demo.url | 3 + .../java/org/jboss/security/srp/SRPParameters.java | 150 ++++ .../org/spongycastle/util/encoders/Base64.java | 121 +++ .../spongycastle/util/encoders/Base64Encoder.java | 298 +++++++ .../org/spongycastle/util/encoders/Encoder.java | 17 + .../util/io/pem/PemGenerationException.java | 26 + .../org/spongycastle/util/io/pem/PemHeader.java | 66 ++ .../org/spongycastle/util/io/pem/PemObject.java | 62 ++ .../util/io/pem/PemObjectGenerator.java | 7 + .../org/spongycastle/util/io/pem/PemWriter.java | 138 ++++ .../java/se/leap/bitmaskclient/AboutActivity.java | 40 + .../java/se/leap/bitmaskclient/ConfigHelper.java | 194 +++++ .../main/java/se/leap/bitmaskclient/Dashboard.java | 479 +++++++++++ .../leap/bitmaskclient/DownloadFailedDialog.java | 94 +++ app/src/main/java/se/leap/bitmaskclient/EIP.java | 623 +++++++++++++++ .../se/leap/bitmaskclient/EipServiceFragment.java | 306 +++++++ .../java/se/leap/bitmaskclient/LeapHttpClient.java | 77 ++ .../java/se/leap/bitmaskclient/LeapSRPSession.java | 341 ++++++++ .../java/se/leap/bitmaskclient/LogInDialog.java | 151 ++++ .../main/java/se/leap/bitmaskclient/PRNGFixes.java | 338 ++++++++ .../main/java/se/leap/bitmaskclient/Provider.java | 212 +++++ .../bitmaskclient/ProviderAPIResultReceiver.java | 56 ++ .../se/leap/bitmaskclient/ProviderListAdapter.java | 114 +++ .../leap/bitmaskclient/ProviderListFragment.java | 234 ++++++ app/src/main/java/se/leap/openvpn/CIDRIP.java | 58 ++ .../main/java/se/leap/openvpn/ConfigParser.java | 569 ++++++++++++++ app/src/main/java/se/leap/openvpn/LICENSE.txt | 24 + app/src/main/java/se/leap/openvpn/LaunchVPN.java | 385 +++++++++ app/src/main/java/se/leap/openvpn/LogWindow.java | 340 ++++++++ .../java/se/leap/openvpn/NetworkSateReceiver.java | 86 ++ app/src/main/java/se/leap/openvpn/OpenVPN.java | 250 ++++++ .../main/java/se/leap/openvpn/OpenVPNThread.java | 130 +++ .../se/leap/openvpn/OpenVpnManagementThread.java | 592 ++++++++++++++ .../main/java/se/leap/openvpn/OpenVpnService.java | 504 ++++++++++++ .../main/java/se/leap/openvpn/ProfileManager.java | 220 ++++++ .../main/java/se/leap/openvpn/ProxyDetection.java | 54 ++ .../main/java/se/leap/openvpn/VPNLaunchHelper.java | 76 ++ app/src/main/java/se/leap/openvpn/VpnProfile.java | 758 ++++++++++++++++++ app/src/main/res/drawable-hdpi/ic_menu_add.png | Bin 0 -> 2194 bytes app/src/main/res/drawable-hdpi/ic_menu_login.png | Bin 0 -> 1656 bytes .../drawable-hdpi/ic_menu_settings_holo_light.png | Bin 0 -> 1227 bytes .../res/drawable-hdpi/ic_menu_trash_holo_light.png | Bin 0 -> 1001 bytes .../res/drawable-hdpi/ic_sysbar_quicksettings.png | Bin 0 -> 773 bytes .../main/res/drawable-hdpi/ic_vpn_disconnected.png | Bin 0 -> 801 bytes app/src/main/res/drawable-hdpi/icon.png | Bin 0 -> 7965 bytes app/src/main/res/drawable-ldpi/ic_menu_add.png | Bin 0 -> 1580 bytes app/src/main/res/drawable-ldpi/ic_menu_login.png | Bin 0 -> 1512 bytes app/src/main/res/drawable-ldpi/ic_stat_vpn.png | Bin 0 -> 461 bytes .../main/res/drawable-ldpi/ic_vpn_disconnected.png | Bin 0 -> 455 bytes app/src/main/res/drawable-ldpi/icon.png | Bin 0 -> 3118 bytes app/src/main/res/drawable-mdpi/ic_menu_add.png | Bin 0 -> 1339 bytes .../drawable-mdpi/ic_menu_settings_holo_light.png | Bin 0 -> 866 bytes .../res/drawable-mdpi/ic_menu_trash_holo_light.png | Bin 0 -> 746 bytes .../res/drawable-mdpi/ic_sysbar_quicksettings.png | Bin 0 -> 653 bytes .../main/res/drawable-mdpi/ic_vpn_disconnected.png | Bin 0 -> 586 bytes app/src/main/res/drawable-mdpi/icon.png | Bin 0 -> 4518 bytes app/src/main/res/drawable-xhdpi/ic_menu_login.png | Bin 0 -> 2178 bytes .../drawable-xhdpi/ic_menu_settings_holo_light.png | Bin 0 -> 1622 bytes .../drawable-xhdpi/ic_menu_trash_holo_light.png | Bin 0 -> 1279 bytes .../res/drawable-xhdpi/ic_sysbar_quicksettings.png | Bin 0 -> 956 bytes .../res/drawable-xhdpi/ic_vpn_disconnected.png | Bin 0 -> 1091 bytes app/src/main/res/drawable-xhdpi/icon.png | Bin 0 -> 11135 bytes app/src/main/res/layout-xlarge/about.xml | 134 ++++ .../main/res/layout-xlarge/client_dashboard.xml | 69 ++ .../configuration_wizard_activity.xml | 27 + .../res/layout-xlarge/eip_service_fragment.xml | 75 ++ app/src/main/res/layout-xlarge/log_in_dialog.xml | 39 + app/src/main/res/layout-xlarge/logwindow.xml | 17 + .../res/layout-xlarge/provider_detail_fragment.xml | 43 + .../res/layout-xlarge/provider_list_fragment.xml | 16 + .../main/res/layout-xlarge/provider_list_item.xml | 44 ++ app/src/main/res/layout/about.xml | 122 +++ app/src/main/res/layout/client_dashboard.xml | 67 ++ .../res/layout/configuration_wizard_activity.xml | 26 + app/src/main/res/layout/eip_service_fragment.xml | 71 ++ app/src/main/res/layout/log_in_dialog.xml | 41 + app/src/main/res/layout/logwindow.xml | 17 + .../main/res/layout/provider_detail_fragment.xml | 40 + app/src/main/res/layout/provider_list_fragment.xml | 15 + app/src/main/res/layout/provider_list_item.xml | 43 + app/src/main/res/menu/client_dashboard.xml | 23 + .../res/menu/configuration_wizard_activity.xml | 15 + app/src/main/res/menu/logmenu.xml | 34 + app/src/main/res/values-ca/arrays.xml | 3 + app/src/main/res/values-ca/strings.xml | 41 + app/src/main/res/values-cs/arrays.xml | 3 + app/src/main/res/values-cs/strings.xml | 79 ++ app/src/main/res/values-de/arrays.xml | 3 + app/src/main/res/values-de/strings.xml | 80 ++ app/src/main/res/values-es/arrays.xml | 3 + app/src/main/res/values-es/strings.xml | 60 ++ app/src/main/res/values-et/arrays.xml | 3 + app/src/main/res/values-et/strings.xml | 66 ++ app/src/main/res/values-fr/arrays.xml | 3 + app/src/main/res/values-fr/strings.xml | 65 ++ app/src/main/res/values-id/arrays.xml | 3 + app/src/main/res/values-id/strings.xml | 80 ++ app/src/main/res/values-it/arrays.xml | 3 + app/src/main/res/values-it/strings.xml | 66 ++ app/src/main/res/values-ja/arrays.xml | 3 + app/src/main/res/values-ja/strings.xml | 82 ++ app/src/main/res/values-ko/arrays.xml | 3 + app/src/main/res/values-ko/strings.xml | 65 ++ app/src/main/res/values-nl/arrays.xml | 3 + app/src/main/res/values-nl/strings.xml | 33 + app/src/main/res/values-no/arrays.xml | 3 + app/src/main/res/values-no/strings.xml | 38 + app/src/main/res/values-ro/arrays.xml | 3 + app/src/main/res/values-ro/strings.xml | 51 ++ app/src/main/res/values-ru/arrays.xml | 3 + app/src/main/res/values-ru/strings.xml | 80 ++ app/src/main/res/values-uk/arrays.xml | 3 + app/src/main/res/values-uk/strings.xml | 81 ++ app/src/main/res/values-zh-rCN/arrays.xml | 3 + app/src/main/res/values-zh-rCN/strings.xml | 52 ++ app/src/main/res/values-zh-rTW/arrays.xml | 3 + app/src/main/res/values-zh-rTW/strings.xml | 52 ++ app/src/main/res/values/arrays.xml | 4 + app/src/main/res/values/attrs.xml | 8 + app/src/main/res/values/strings.xml | 142 ++++ app/src/main/res/values/styles.xml | 37 + app/src/main/res/values/untranslatable.xml | 19 + .../se/leap/bitmaskclient/ConfigurationWizard.java | 574 ++++++++++++++ .../se/leap/bitmaskclient/NewProviderDialog.java | 117 +++ .../java/se/leap/bitmaskclient/ProviderAPI.java | 860 ++++++++++++++++++++ .../leap/bitmaskclient/ProviderDetailFragment.java | 115 +++ .../se/leap/bitmaskclient/ProviderListContent.java | 110 +++ .../res/layout-xlarge/new_provider_dialog.xml | 19 + app/src/release/res/layout/new_provider_dialog.xml | 18 + 149 files changed, 15119 insertions(+) create mode 100644 app/src/androidTest/java/se/leap/bitmaskclient/test/ConnectionManager.java create mode 100644 app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java create mode 100644 app/src/androidTest/java/se/leap/bitmaskclient/test/testDashboard.java create mode 100644 app/src/androidTest/java/se/leap/bitmaskclient/test/testLeapSRPSession.java create mode 100644 app/src/debug/AndroidManifest.xml create mode 100644 app/src/debug/assets/urls/cdev.bitmask.net.url create mode 100644 app/src/debug/assets/urls/dev.bitmask.net.url create mode 100644 app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java create mode 100644 app/src/debug/java/se/leap/bitmaskclient/NewProviderDialog.java create mode 100644 app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java create mode 100644 app/src/debug/java/se/leap/bitmaskclient/ProviderDetailFragment.java create mode 100644 app/src/debug/java/se/leap/bitmaskclient/ProviderListContent.java create mode 100644 app/src/debug/res/layout-xlarge/new_provider_dialog.xml create mode 100644 app/src/debug/res/layout/new_provider_dialog.xml create mode 100644 app/src/debug/res/values/strings.xml create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/assets/minivpn.armeabi create mode 100644 app/src/main/assets/minivpn.armeabi-v7a create mode 100644 app/src/main/assets/minivpn.mips create mode 100644 app/src/main/assets/minivpn.x86 create mode 100644 app/src/main/assets/urls/bitmask demo.url create mode 100644 app/src/main/java/org/jboss/security/srp/SRPParameters.java create mode 100644 app/src/main/java/org/spongycastle/util/encoders/Base64.java create mode 100644 app/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java create mode 100644 app/src/main/java/org/spongycastle/util/encoders/Encoder.java create mode 100644 app/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java create mode 100644 app/src/main/java/org/spongycastle/util/io/pem/PemHeader.java create mode 100644 app/src/main/java/org/spongycastle/util/io/pem/PemObject.java create mode 100644 app/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java create mode 100644 app/src/main/java/org/spongycastle/util/io/pem/PemWriter.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/AboutActivity.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/Dashboard.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/EIP.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/LeapHttpClient.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/LogInDialog.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/PRNGFixes.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/Provider.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/ProviderAPIResultReceiver.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java create mode 100644 app/src/main/java/se/leap/openvpn/CIDRIP.java create mode 100644 app/src/main/java/se/leap/openvpn/ConfigParser.java create mode 100644 app/src/main/java/se/leap/openvpn/LICENSE.txt create mode 100644 app/src/main/java/se/leap/openvpn/LaunchVPN.java create mode 100644 app/src/main/java/se/leap/openvpn/LogWindow.java create mode 100644 app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java create mode 100644 app/src/main/java/se/leap/openvpn/OpenVPN.java create mode 100644 app/src/main/java/se/leap/openvpn/OpenVPNThread.java create mode 100644 app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java create mode 100644 app/src/main/java/se/leap/openvpn/OpenVpnService.java create mode 100644 app/src/main/java/se/leap/openvpn/ProfileManager.java create mode 100644 app/src/main/java/se/leap/openvpn/ProxyDetection.java create mode 100644 app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java create mode 100644 app/src/main/java/se/leap/openvpn/VpnProfile.java create mode 100644 app/src/main/res/drawable-hdpi/ic_menu_add.png create mode 100644 app/src/main/res/drawable-hdpi/ic_menu_login.png create mode 100644 app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png create mode 100644 app/src/main/res/drawable-hdpi/ic_menu_trash_holo_light.png create mode 100644 app/src/main/res/drawable-hdpi/ic_sysbar_quicksettings.png create mode 100644 app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png create mode 100644 app/src/main/res/drawable-hdpi/icon.png create mode 100644 app/src/main/res/drawable-ldpi/ic_menu_add.png create mode 100644 app/src/main/res/drawable-ldpi/ic_menu_login.png create mode 100644 app/src/main/res/drawable-ldpi/ic_stat_vpn.png create mode 100644 app/src/main/res/drawable-ldpi/ic_vpn_disconnected.png create mode 100644 app/src/main/res/drawable-ldpi/icon.png create mode 100644 app/src/main/res/drawable-mdpi/ic_menu_add.png create mode 100644 app/src/main/res/drawable-mdpi/ic_menu_settings_holo_light.png create mode 100644 app/src/main/res/drawable-mdpi/ic_menu_trash_holo_light.png create mode 100644 app/src/main/res/drawable-mdpi/ic_sysbar_quicksettings.png create mode 100644 app/src/main/res/drawable-mdpi/ic_vpn_disconnected.png create mode 100644 app/src/main/res/drawable-mdpi/icon.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_menu_login.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_menu_settings_holo_light.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_menu_trash_holo_light.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_sysbar_quicksettings.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_vpn_disconnected.png create mode 100644 app/src/main/res/drawable-xhdpi/icon.png create mode 100644 app/src/main/res/layout-xlarge/about.xml create mode 100644 app/src/main/res/layout-xlarge/client_dashboard.xml create mode 100644 app/src/main/res/layout-xlarge/configuration_wizard_activity.xml create mode 100644 app/src/main/res/layout-xlarge/eip_service_fragment.xml create mode 100644 app/src/main/res/layout-xlarge/log_in_dialog.xml create mode 100644 app/src/main/res/layout-xlarge/logwindow.xml create mode 100644 app/src/main/res/layout-xlarge/provider_detail_fragment.xml create mode 100644 app/src/main/res/layout-xlarge/provider_list_fragment.xml create mode 100644 app/src/main/res/layout-xlarge/provider_list_item.xml create mode 100644 app/src/main/res/layout/about.xml create mode 100644 app/src/main/res/layout/client_dashboard.xml create mode 100644 app/src/main/res/layout/configuration_wizard_activity.xml create mode 100644 app/src/main/res/layout/eip_service_fragment.xml create mode 100644 app/src/main/res/layout/log_in_dialog.xml create mode 100644 app/src/main/res/layout/logwindow.xml create mode 100644 app/src/main/res/layout/provider_detail_fragment.xml create mode 100644 app/src/main/res/layout/provider_list_fragment.xml create mode 100644 app/src/main/res/layout/provider_list_item.xml create mode 100644 app/src/main/res/menu/client_dashboard.xml create mode 100644 app/src/main/res/menu/configuration_wizard_activity.xml create mode 100644 app/src/main/res/menu/logmenu.xml create mode 100644 app/src/main/res/values-ca/arrays.xml create mode 100644 app/src/main/res/values-ca/strings.xml create mode 100644 app/src/main/res/values-cs/arrays.xml create mode 100644 app/src/main/res/values-cs/strings.xml create mode 100644 app/src/main/res/values-de/arrays.xml create mode 100644 app/src/main/res/values-de/strings.xml create mode 100644 app/src/main/res/values-es/arrays.xml create mode 100644 app/src/main/res/values-es/strings.xml create mode 100644 app/src/main/res/values-et/arrays.xml create mode 100644 app/src/main/res/values-et/strings.xml create mode 100644 app/src/main/res/values-fr/arrays.xml create mode 100644 app/src/main/res/values-fr/strings.xml create mode 100644 app/src/main/res/values-id/arrays.xml create mode 100644 app/src/main/res/values-id/strings.xml create mode 100644 app/src/main/res/values-it/arrays.xml create mode 100644 app/src/main/res/values-it/strings.xml create mode 100644 app/src/main/res/values-ja/arrays.xml create mode 100644 app/src/main/res/values-ja/strings.xml create mode 100644 app/src/main/res/values-ko/arrays.xml create mode 100644 app/src/main/res/values-ko/strings.xml create mode 100644 app/src/main/res/values-nl/arrays.xml create mode 100644 app/src/main/res/values-nl/strings.xml create mode 100644 app/src/main/res/values-no/arrays.xml create mode 100644 app/src/main/res/values-no/strings.xml create mode 100644 app/src/main/res/values-ro/arrays.xml create mode 100644 app/src/main/res/values-ro/strings.xml create mode 100644 app/src/main/res/values-ru/arrays.xml create mode 100644 app/src/main/res/values-ru/strings.xml create mode 100644 app/src/main/res/values-uk/arrays.xml create mode 100644 app/src/main/res/values-uk/strings.xml create mode 100644 app/src/main/res/values-zh-rCN/arrays.xml create mode 100644 app/src/main/res/values-zh-rCN/strings.xml create mode 100644 app/src/main/res/values-zh-rTW/arrays.xml create mode 100644 app/src/main/res/values-zh-rTW/strings.xml create mode 100644 app/src/main/res/values/arrays.xml create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/values/untranslatable.xml create mode 100644 app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java create mode 100644 app/src/release/java/se/leap/bitmaskclient/NewProviderDialog.java create mode 100644 app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java create mode 100644 app/src/release/java/se/leap/bitmaskclient/ProviderDetailFragment.java create mode 100644 app/src/release/java/se/leap/bitmaskclient/ProviderListContent.java create mode 100644 app/src/release/res/layout-xlarge/new_provider_dialog.xml create mode 100644 app/src/release/res/layout/new_provider_dialog.xml (limited to 'app/src') diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/ConnectionManager.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/ConnectionManager.java new file mode 100644 index 00000000..f1cbff19 --- /dev/null +++ b/app/src/androidTest/java/se/leap/bitmaskclient/test/ConnectionManager.java @@ -0,0 +1,33 @@ +package se.leap.bitmaskclient.test; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.util.Log; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class ConnectionManager { + static void setMobileDataEnabled(boolean enabled, Context context) { + final ConnectivityManager conman = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + Method[] methods = conman.getClass().getMethods(); + for (Method method : methods) { + if (method.getName().equals("setMobileDataEnabled")) { + method.setAccessible(true); + try { + method.invoke(conman, enabled); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + + static void setWifiEnabled(boolean enabled, Context context) { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + wifiManager.setWifiEnabled(enabled); + } +} diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java new file mode 100644 index 00000000..847d8cdd --- /dev/null +++ b/app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java @@ -0,0 +1,65 @@ +package se.leap.bitmaskclient.test; + +import android.test.ActivityInstrumentationTestCase2; +import android.widget.ListView; +import com.jayway.android.robotium.solo.Solo; +import java.io.IOException; +import se.leap.bitmaskclient.AboutActivity; +import se.leap.bitmaskclient.ConfigurationWizard; +import se.leap.bitmaskclient.ProviderDetailFragment; +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.test.ConnectionManager; + +public class testConfigurationWizard extends ActivityInstrumentationTestCase2 { + + private Solo solo; + private static int added_providers; + + public testConfigurationWizard() { + super(ConfigurationWizard.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + solo = new Solo(getInstrumentation(), getActivity()); + ConnectionManager.setMobileDataEnabled(true, solo.getCurrentActivity().getApplicationContext()); + } + + @Override + protected void tearDown() throws Exception { + solo.finishOpenedActivities(); + } + + public void testListProviders() throws IOException { + assertEquals(solo.getCurrentViews(ListView.class).size(), 1); + + int number_of_available_providers = solo.getCurrentViews(ListView.class).get(0).getCount(); + + assertEquals("Number of available providers differ", solo.getCurrentActivity().getAssets().list("urls").length + added_providers, number_of_available_providers); + } + + public void testSelectProvider() { + solo.clickOnText("bitmask"); + assertTrue("Provider details dialog did not appear", solo.waitForFragmentByTag(ProviderDetailFragment.TAG, 60*1000)); + } + + public void testAddNewProvider() { + solo.clickOnActionBarItem(R.id.new_provider); + solo.enterText(0, "dev.bitmask.net"); + solo.clickOnCheckBox(0); + solo.clickOnText(solo.getString(R.string.save)); + added_providers = added_providers+1; + assertTrue("Provider details dialog did not appear", solo.waitForFragmentByTag(ProviderDetailFragment.TAG, 60*1000)); + solo.goBack(); + } + + public void testShowAbout() { + solo.clickOnMenuItem(solo.getString(R.string.about)); + assertTrue("Provider details dialog did not appear", solo.waitForActivity(AboutActivity.class)); + } + + public void testShowSettings() { + //TODO We still don't have the settings button + } +} diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/testDashboard.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/testDashboard.java new file mode 100644 index 00000000..269f1d18 --- /dev/null +++ b/app/src/androidTest/java/se/leap/bitmaskclient/test/testDashboard.java @@ -0,0 +1,94 @@ +package se.leap.bitmaskclient.test; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.provider.Settings; +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; +import com.jayway.android.robotium.solo.Solo; +import se.leap.bitmaskclient.ConfigurationWizard; +import se.leap.bitmaskclient.Dashboard; +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.test.ConnectionManager; + +public class testDashboard extends ActivityInstrumentationTestCase2 { + + private Solo solo; + + public testDashboard() { + super(Dashboard.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + solo = new Solo(getInstrumentation(), getActivity()); + ConnectionManager.setMobileDataEnabled(true, solo.getCurrentActivity().getApplicationContext()); + } + + @Override + protected void tearDown() throws Exception { + solo.finishOpenedActivities(); + } + + /** + * This test will fail if Android does not trust VPN connection. + * I cannot automate that dialog. + */ + public void testOnOffOpenVpn() { + solo.clickOnView(solo.getView(R.id.eipSwitch)); + if(!solo.waitForText(getActivity().getString(R.string.eip_status_start_pending))) + fail(); + if(!solo.waitForText(getActivity().getString(R.string.state_auth))) + fail(); + if(!solo.waitForText(getActivity().getString(R.string.eip_state_connected), 1, 30*1000)) + fail(); + + solo.clickOnView(solo.getView(R.id.eipSwitch)); + if(!solo.waitForText(getActivity().getString(R.string.eip_state_not_connected))) + fail(); + + ConnectionManager.setMobileDataEnabled(false, solo.getCurrentActivity().getApplicationContext()); + + solo.clickOnView(solo.getView(R.id.eipSwitch)); + if(!solo.waitForText(getActivity().getString(R.string.eip_status_start_pending))) + fail(); + if(!solo.waitForText(getActivity().getString(R.string.state_nonetwork))) + fail(); + + } + + public void testLogInAndOut() { + long miliseconds_to_log_in = 40 * 1000; + solo.clickOnActionBarItem(R.id.login_button); + solo.enterText(0, "parmegvtest1"); + solo.enterText(1, " S_Zw3'-"); + solo.clickOnText("Log In"); + solo.waitForDialogToClose(); + solo.waitForDialogToClose(miliseconds_to_log_in); + if(!solo.waitForText(getActivity().getString(R.string.succesful_authentication_message))) + fail(); + + solo.clickOnActionBarItem(R.string.logout_button); + if(!solo.waitForDialogToClose()) + fail(); + } + + public void testShowAbout() { + solo.clickOnMenuItem(getActivity().getString(R.string.about)); + solo.waitForText(getActivity().getString(R.string.repository_url_text)); + solo.goBack(); + + solo.clickOnMenuItem(getActivity().getString(R.string.about)); + solo.waitForText(getActivity().getString(R.string.repository_url_text)); + solo.goBack(); + } + + public void testSwitchProvider() { + solo.clickOnMenuItem(getActivity().getString(R.string.switch_provider_menu_option)); + solo.waitForActivity(ConfigurationWizard.class); + solo.goBack(); + } +} diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/testLeapSRPSession.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/testLeapSRPSession.java new file mode 100644 index 00000000..88d70da4 --- /dev/null +++ b/app/src/androidTest/java/se/leap/bitmaskclient/test/testLeapSRPSession.java @@ -0,0 +1,636 @@ +package se.leap.bitmaskclient.test; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import org.jboss.security.srp.SRPParameters; + +import android.test.suitebuilder.annotation.SmallTest; + +import se.leap.bitmaskclient.ConfigHelper; +import se.leap.bitmaskclient.LeapSRPSession; + +import junit.framework.TestCase; + +public class testLeapSRPSession extends TestCase { + + public testLeapSRPSession(String name) { + super(name); + } + + protected void setUp() throws Exception { + super.setUp(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testExponential() { + byte[] expected_A; + byte[] a_byte; + SRPParameters params; + LeapSRPSession client; + + /* Test 1: abytes = 4 */ + expected_A = new BigInteger("44eba0239ddfcc5a488d208df32a89eb00e93e6576b22ba2e4410085a413cf64e9c2f08ebc36a788a0761391150ad4a0507ca43f9ca659e2734f0457a85358c0bb39fa87183c9d3f9f8a3b148dab6303a4e796294f3e956472ba0e2ea5697382acd93c8b8f1b3a7a9d8517eebffd6301bfc8de7f7b701f0878a71faae1e25ad4", 16).toByteArray(); + String username = "username", + password = "password", + salt = "64c3289d04a6ecad", + a = "3565fdc2"; + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + + byte[] A = client.exponential(); + + assertTrue(Arrays.equals(A, expected_A)); + + /* Test 1: abytes = 5 */ + a = "67c152a3"; + expected_A = new BigInteger("11acfacc08178d48f95c0e69adb11f6d144dd0980ee6e44b391347592e3bd5e9cb841d243b3d9ac2adb25b367a2558e8829b22dcef96c0934378412383ccf95141c3cb5f17ada20f53a0225f56a07f2b0c0469ed6bbad3646f7b71bdd4bedf5cc6fac244b26d3195d8f55877ff94a925b0c0c8f7273eca733c0355b38360442e", 16).toByteArray(); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + + A = client.exponential(); + + assertTrue(Arrays.equals(A, expected_A)); + } + + public void testResponse() throws NoSuchAlgorithmException { + /* Test 1: with intermediate checks */ + byte[] expected_A = trim(new BigInteger("884380f70a62193bbe3589c4e1dbdc4467b6b5a1b4486e4b779023506fc1f885ae26fa4a5d817b3f38a35f3487b147b82d4bd0069faa64fdc845f7494a78251709e212698e42ced44b0f3849adc73f467afcb26983bd13bdc38906b178003373ddd0ac1d38ce8a39ffa3a7795787207a129a784f4b65ce0b302eb1bcf4045883", 16).toByteArray()); + byte[] expected_x = new BigInteger("4cb937fd74ee3bb53b79a3174d0c07c14131de9c825897cbca52154e74200602", 16).toByteArray(); + byte[] expected_M1 = trim(new BigInteger("e6a8efca2c07ef24e0b69be2d4d4a7e74742a4db7a92228218fec0008f7cc94b", 16).toByteArray()); + String username = "username", + password = "password", + salt = "64c3289d04a6ecad", + a = "8c911355"; + byte[] a_byte = new BigInteger(a, 16).toByteArray(); + SRPParameters params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + LeapSRPSession client = new LeapSRPSession(username, password, params, a_byte); + + byte[] x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); + assertTrue(Arrays.equals(x, expected_x)); + + byte[] A = client.exponential(); + assertTrue(Arrays.equals(A, expected_A)); + + String B = "bc745ba25564fc312f44ea09fb663aa6d95867772e412a6a23f1bc24183e54b32f134372c560f4b3fda19ba7a56b0f84fdcdecc22be6fd256639e918e019691c40a39aa5c9631820e42b28da61b8c75b45afae9d77d63ac8f4dda093762be4a890fbd86061dbd7e5e7c03c4dacde769e0f564df00403e449c0535537f1ba7263"; + + byte[] M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + assertTrue(Arrays.equals(M1, expected_M1)); + + /* Test 2: no intermediate checks */ + expected_A = trim(new BigInteger("9ffc407afd7e7ecd32a8ea68aa782b0254a7e2197a955b5aa646fc1fc43ff6ef2239f01b7d5b82f152c870d3e69f3321878ca2acda06dd8fb6ce02f41c7ed48061c78697b01cf353f4222311334c707358b6ec067e317527316bfa85b5ec74537e38b5b14c1100d14c96320f385e5b1dcccde07e728c7ef624353167a29ae461", 16).toByteArray()); + expected_M1 = trim(new BigInteger("c3203ec1dd55c96038276456c18c447fb4d2a2f896c73c31d56da1781cae79a8", 16).toByteArray()); + a = "38d5b211"; + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); + A = client.exponential(); + + B = "b8ca7d93dbe2478966ffe025a9f2fb43b9995ce04af9523ea9a3fa4b132136076aa66ead1597c3da23f477ce1cfaf68b5dcc94e146db06cf8391d14a76ce53aab86067b13c93b135d7be6275669b3f51afec6cc41f19e0afca7c3ad5c4d6ee4c09d4b11bcd12e26c727ee56d173b92eea6926e72cc73deebe12dd6f30f44db8a"; + M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + /* Test 3: With intermediate values */ + expected_M1 = new BigInteger("4c01f65a9bb00f95e435593083040ae1e59e59800c598b42de821c21f3a35223", 16).toByteArray(); + expected_A = new BigInteger("1bceab3047a6f84495fdd5b4dbe891f0b30f870d7d4e38eaef728f6a7d4e9342d8dae8502fdae4f16b718d2e916a38b16b6def45559a5ebae417a1b115ba6b6a0451c7ff174c3e2507d7d1f18ef646fd065bc9ba165a2a0ae4d6da54f060427178b95b0eff745f5c3f8c4f19ea35addc3ce0daf2aca3328c98bafcf98286d115", 16).toByteArray(); + B = "41a7b384f2f52312fa79b9dc650ae894f543aea49800cf9477fbcf63e39cbfe6d422f7126777d645cdf749276a3ae9eb6dfcfdb887f8f60ac4094a343013fcebbd40e95b3153f403ab7bb21ea1315aa018bab6ab84017fcb084b3870a8bf1cb717b39c9a28177c61ce7d1738379be9d192dd9793b50ebc3afabe5e78b0a4b017"; + a = "36ee80ec"; + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); + A = client.exponential(); + + M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + } + + public void testCalculateV() throws NoSuchAlgorithmException { + String expected_v = "502f3ffddc78b866330550c2c60ebd68427c1793237d770e6390d1f794abd47b6786fa5025728d1ca4ec24cfc7a89808278330099ad66456a7c9c88be570b928f9825ac2ecdee31792335f7fa5fc9a78b692c487aa400c7d5cc5c1f2a3a74634c4afa0159600bbf22bf6dfb1e0d85061e55ce8df6243758066503bcf51c83848cf7184209731f89a90d888934c75798828859babe73c17009bf827723fc1bcd0"; + + BigInteger k = new BigInteger("bf66c44a428916cad64aa7c679f3fd897ad4c375e9bbb4cbf2f5de241d618ef0", 16); + BigInteger g = new BigInteger("2", 16); + BigInteger N = new BigInteger(ConfigHelper.NG_1024, 16); + BigInteger x = new BigInteger("4cb937fd74ee3bb53b79a3174d0c07c14131de9c825897cbca52154e74200602", 16); + + BigInteger v = k.multiply(g.modPow(x, N)); // g^x % N + + assertEquals(v.toString(16), expected_v); + assertTrue(Arrays.equals(v.toByteArray(), new BigInteger(expected_v, 16).toByteArray())); + } + + public void testGetU() throws NoSuchAlgorithmException { + /* Test 1 */ + String Astr = "46b1d1fe038a617966821bd5bb6af967be1bcd6f54c2db5a474cb80b625870e4616953271501a82198d0c14e72b95cdcfc9ec867027b0389aacb313319d4e81604ccf09ce7841dc333be2e03610ae46ec0c8e06b8e86975e0984cae4d0b61c51f1fe5499a4d4d42460261a3e134f841f2cef4d68a583130ee8d730e0b51a858f"; + String Bstr = "5e1a9ac84b1d9212a0d8f8fe444a34e7da4556a1ef5aebc043ae7276099ccdb305fd7e1c179729e24b484a35c0e33b6a898477590b93e9a4044fc1b8d6bc73db8ac7778f546b25ec3f22e92ab7144e5b974dc58e82a333262063062b6944a2e4393d2a087e4906e6a8cfa0fdfd8a5e5930b7cdb45435cbee7c49dfa1d1216881"; + String ustr = "759c3cfb6bfaccf07eeb8e46fe6ea290291d4d32faca0681830a372983ab0a61"; + + byte[] Abytes = new BigInteger(Astr, 16).toByteArray(); + byte[] Bbytes = new BigInteger(Bstr, 16).toByteArray(); + byte[] expected_u = new BigInteger(ustr, 16).toByteArray(); + + MessageDigest u_digest = MessageDigest.getInstance("SHA256"); + u_digest.update(trim(Abytes)); + u_digest.update(trim(Bbytes)); + byte[] u = new BigInteger(1, u_digest.digest()).toByteArray(); + + assertTrue(Arrays.equals(expected_u, u)); + + /* Test 2 */ + Astr = "c4013381bdb2fdd901944b9d823360f367c52635b576b9a50d2db77141d357ed391c3ac5fa452c2bbdc35f96bfed21df61627b40aed8f67f21ebf81e5621333f44049d6c9f6ad36464041438350e1f86000a8e3bfb63d4128c18322d2517b0d3ead63fd504a9c8f2156d46e64268110cec5f3ccab54a21559c7ab3ad67fedf90"; + Bstr = "e5d988752e8f265f01b98a1dcdecc4b685bd512e7cd9507f3c29f206c27dac91e027641eed1765c4603bbd7a9aa7fac300ef67dafe611ba2dbe29a32d83d486296f328d38b44c0c211d01d3fe422aac168b6850c87782338969c54594fc87804d4db34910ad4b5452a81027842ac8d8d8288fd44872e4c719ac8fb971d0a33e1"; + ustr = "6510328f913b81ba662e564ee5afb7c395ea27c3c0276fc5ca51f0edecd4baf1"; + + Abytes = new BigInteger(Astr, 16).toByteArray(); + Bbytes = new BigInteger(Bstr, 16).toByteArray(); + expected_u = new BigInteger(ustr, 16).toByteArray(); + expected_u = trim(expected_u); + + u_digest = MessageDigest.getInstance("SHA-256"); + u_digest.update(trim(Abytes)); + u_digest.update(trim(Bbytes)); + u = new BigInteger(1, u_digest.digest()).toByteArray(); + u = trim(u); + + assertTrue(Arrays.equals(expected_u, u)); + + /* Test 3 */ + Astr = "d13973fe4e0e13423cd036caf0912e23a1f9b0c23966f5a5897c8ff17c5cbac8bab7f07d9ac4ee47396a7c68e80ce854c84f243148521277900aaa132a7b93b61e54d742d7f36edb4cdef54bc78cca69ac72653a7ae0fc47ec1e9a84024ea9487a61357e28eddc185e4fe01388e64e6b8f688dd74471d56dd244204522e08483"; + Bstr = "a6701686d9d987a43f06e8497330c8add8febd191a7a975bced0d058eb03ccc6805263349363b2d54ac435b01155dc41c6067287d9b93e3637ab3b7e8bc7d9cf38d9fdbb2ca9ee8ba1946a46cb555cb7dafcc177fcf7a4b0eb1e5db2249949c1fd15e0b7c1b3616f9e2649bdf074ed841efbdc9f29ee8c8bfcedeaed3dc49378"; + ustr = "78414ec80cf44225e7ed386dcf2ceb89837327ccae11b761fc77d48c0307977"; + + Abytes = new BigInteger(Astr, 16).toByteArray(); + Bbytes = new BigInteger(Bstr, 16).toByteArray(); + expected_u = new BigInteger(ustr, 16).toByteArray(); + expected_u = trim(expected_u); + + u_digest = MessageDigest.getInstance("SHA-256"); + u_digest.update(trim(Abytes)); + u_digest.update(trim(Bbytes)); + u = new BigInteger(1, u_digest.digest()).toByteArray(); + u = trim(u); + + assertTrue(Arrays.equals(expected_u, u)); + + /* Test 4 */ + Astr = "ee8bc0cb97dd9c9937759658ff9d791df1dd57b48c5febc2e98af028d0e36eaddf1a3fc555f2bcd6456827e8c7b07ec02a1f365457843bda226bfc1a55c4776879f9df6c916810131ec65a3a4cf473c6a34299d64c91cf26542ea0fc059d24422fc783460c3fafe26bf6f7c24904ae1c5a6421e2f5315030ab007ce8f2c2fd97"; + Bstr = "95ecbd13b28c7f38318fd664ee97d9e853b0d6e9cbff9a3775a3cc5d5077ffc146aec70d9439e75c19a34b67368b8bd7035ba6254e0a260d99b1e253aae2e0d8f4a13e1ed472f3ef0e3086300cd15d059f6be7d7141ee09071b1c5e5d1c83b250a3c8f1a587f8fe59d49aaeb2cfc7e13a5a58bc76cc8baf7f6a647982c67ee49"; + ustr = "e28737c7307c84e4d0866b7cf882f22852a764b109634f77a5eb986a96ffcf9a"; + + Abytes = new BigInteger(Astr, 16).toByteArray(); + Bbytes = new BigInteger(Bstr, 16).toByteArray(); + expected_u = new BigInteger(ustr, 16).toByteArray(); + expected_u = trim(expected_u); + assertEquals(new BigInteger(1, expected_u).toString(16), ustr); + + u_digest = MessageDigest.getInstance("SHA-256"); + u_digest.update(trim(Abytes)); + u_digest.update(trim(Bbytes)); + u = new BigInteger(1, u_digest.digest()).toByteArray(); + u = trim(u); + + assertTrue(Arrays.equals(expected_u, u)); + } + + @SmallTest + public void testCalculatePasswordHash() throws UnsupportedEncodingException, NoSuchAlgorithmException { + String salt_str = "", username_str = "", password_str = ""; + String expected_inner = "cfb9ae3ec5433076889c4fe5663926e20bf570cc7950a51c889a314fab2f5ed716bde9c1cc91be14", + expected_x = "9736a5e386a18f35bb08cac0f7c70bdbe120f2efe019874d0eb23b85b1955858"; + + /* Test 1 */ + salt_str = "cfb9ae3ec5433076"; username_str = "nostradamus"; password_str = "$[[//jjiilajfewahug43a89y¿"; + password_str = password_str.replaceAll("\\\\", "\\\\\\\\"); + // Calculate x = H(s | H(U | ':' | password)) + MessageDigest x_digest = MessageDigest.getInstance("SHA-256"); + + // Try to convert the username to a byte[] using UTF-8 + byte[] user = null; + byte[] password_bytes = null; + byte[] colon = {}; + + String encoding = "UTF-8"; + encoding = "ISO-8859-1"; + user = ConfigHelper.trim(username_str.getBytes(encoding)); + colon = ConfigHelper.trim(":".getBytes(encoding)); + password_bytes = ConfigHelper.trim(password_str.getBytes(encoding)); + + // Build the hash + x_digest.update(user); + x_digest.update(colon); + x_digest.update(password_bytes); + byte[] h = x_digest.digest(); + byte[] salt_bytes = ConfigHelper.trim(new BigInteger(salt_str, 16).toByteArray()); + + x_digest.reset(); + x_digest.update(salt_bytes); + x_digest.update(h); + byte[] x_digest_bytes = x_digest.digest(); + assertTrue(new BigInteger(1, x_digest_bytes).toString(16).equalsIgnoreCase(expected_x)); + } + + public void testCalculateS() throws NoSuchAlgorithmException { + String expected_S = "34d71467d0a30c5787e6b4384c7176a724df28f6c1d3b0b7738238621be19080942ca5d4454ab3e5664d42970ea2d42d1188ab246afb1475c481111f30886c79cc99ddd1d711034bc4ac57a0c134fef40470e1ac3386f39abcd1bf9d0cc758a1f38a87b2f7261018cdc6191cd941292c77c320b3c664a9bb1325dd7a2bf62db4"; + + String B_string = "d390da0991b84e20918764aed9bccf36fc70863f08a1d3fa0aecd9610d8c40d1895afaf3aa9b4582fb5bc673037dfc1bf0ca7d3d190a807dec563fe57d596b3551ec8a6461e0621b77fabfaa29234187063e715e2706522c65f35d89f3914a7cdf40ddd240ef3d22b5534469cf6b3b31e158f817dcf5b6b44b0914d4787d027e"; + String A_string = "c6385e56b9cb8987df3c58ffe6c47bfcd9f9d52f02b68048d72135a8522a0919b41683e3c732a0874e0555b8f288fa067842b1c40a56f1a7d910ad125e71238cb14ba47838f881cabf735d5414f4fcdd6855943621c271c5f013753d439fb124674c79e30d849e9178c780f7c25aded12f053ae41be68289787caf7c3cbb07a4"; + String a_string = "fc1c2d8d"; + + BigInteger A = new BigInteger(A_string, 16); + byte[] Bbytes = new BigInteger(B_string, 16).toByteArray(); + BigInteger a = new BigInteger(a_string, 16); + BigInteger g = ConfigHelper.G; + BigInteger N = new BigInteger(ConfigHelper.NG_1024, 16); + BigInteger k = new BigInteger("bf66c44a428916cad64aa7c679f3fd897ad4c375e9bbb4cbf2f5de241d618ef0", 16); + BigInteger x = new BigInteger("4cb937fd74ee3bb53b79a3174d0c07c14131de9c825897cbca52154e74200602", 16); + + /* GetU */ + MessageDigest u_digest = MessageDigest.getInstance("SHA256"); + u_digest.update(trim(A.toByteArray())); + u_digest.update(trim(Bbytes)); + byte[] ub = new BigInteger(1, u_digest.digest()).toByteArray(); + + BigInteger v = k.multiply(g.modPow(x, N)); // g^x % N + String expected_v = "502f3ffddc78b866330550c2c60ebd68427c1793237d770e6390d1f794abd47b6786fa5025728d1ca4ec24cfc7a89808278330099ad66456a7c9c88be570b928f9825ac2ecdee31792335f7fa5fc9a78b692c487aa400c7d5cc5c1f2a3a74634c4afa0159600bbf22bf6dfb1e0d85061e55ce8df6243758066503bcf51c83848cf7184209731f89a90d888934c75798828859babe73c17009bf827723fc1bcd0"; + String v_string = v.toString(16); + + BigInteger B = new BigInteger(1, Bbytes); + BigInteger u = new BigInteger(1, ub); + String expected_u_string = "2d36f816df24da7b904c904a7e2a2500511df118ced26bda92a63aca792c93b"; + String u_string = u.toString(16); + + BigInteger B_v = B.subtract(v); + BigInteger a_ux = a.add(u.multiply(x)); + byte[] a_ux_byte= a_ux.toByteArray(); + String expected_a_aux_string = "d8d0843a60d471ab46a761b775f8fd28bbbd8affbc704424c79b822ac36b1177925404db910072737df3c5083cf20ebd2f08e1381d80d91e4f6fc99f592203"; + byte[] expected_a_aux_byte = new BigInteger(expected_a_aux_string, 16).toByteArray(); + + BigInteger S = B_v.modPow(a_ux, N); + + byte[] expected_S_bytes = new BigInteger(expected_S, 16).toByteArray(); + byte[] S_bytes = S.toByteArray(); + assertTrue(Arrays.equals(S_bytes, expected_S_bytes)); + assertEquals(S.toString(16), expected_S); + } + + public void testXor() throws Exception { + String expected_xor_string = "928ade491bc87bba9eb578701d44d30ed9080e60e542ba0d3b9c20ded9f592bf"; + byte[] expected_xor = new BigInteger(expected_xor_string, 16).toByteArray(); + + byte[] nbytes = trim(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray()); + byte[] gbytes = trim(ConfigHelper.G.toByteArray()); + + byte[] ndigest = trim(MessageDigest.getInstance("SHA-256").digest(nbytes)); + byte[] gdigest = MessageDigest.getInstance("SHA-256").digest(gbytes); + + BigInteger ndigest_bigInteger = new BigInteger(1, ndigest); + String expected_ndigest_string = "494b6a801b379f37c9ee25d5db7cd70ffcfe53d01b7c9e4470eaca46bda24b39"; + String ndigest_string = ndigest_bigInteger.toString(16); + assertEquals(ndigest_string, expected_ndigest_string); + + BigInteger gdigest_bigInteger = new BigInteger(1, gdigest); + String xor_string = ndigest_bigInteger.xor(gdigest_bigInteger).toString(16); + + byte[] xor = new BigInteger(xor_string, 16).toByteArray(); + + assertTrue(Arrays.equals(expected_xor, xor)); + } + + public void testVerify() throws NoSuchAlgorithmException { + byte[] expected_A = trim(new BigInteger("884380f70a62193bbe3589c4e1dbdc4467b6b5a1b4486e4b779023506fc1f885ae26fa4a5d817b3f38a35f3487b147b82d4bd0069faa64fdc845f7494a78251709e212698e42ced44b0f3849adc73f467afcb26983bd13bdc38906b178003373ddd0ac1d38ce8a39ffa3a7795787207a129a784f4b65ce0b302eb1bcf4045883", 16).toByteArray()); + byte[] expected_x = new BigInteger("4cb937fd74ee3bb53b79a3174d0c07c14131de9c825897cbca52154e74200602", 16).toByteArray(); + byte[] expected_M1 = trim(new BigInteger("e6a8efca2c07ef24e0b69be2d4d4a7e74742a4db7a92228218fec0008f7cc94b", 16).toByteArray()); + byte[] expected_M2 = trim(new BigInteger("6402e108415ab4a7cd223ec435570614c8aacc09fcf081ade2dc00275e90ceee", 16).toByteArray()); + String username = "username", + password = "password", + salt = "64c3289d04a6ecad", + a = "8c911355"; + byte[] a_byte = new BigInteger(a, 16).toByteArray(); + SRPParameters params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + LeapSRPSession client = new LeapSRPSession(username, password, params, a_byte); + + byte[] x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); + assertTrue(Arrays.equals(x, expected_x)); + + byte[] A = client.exponential(); + assertTrue(Arrays.equals(A, expected_A)); + + String B = "bc745ba25564fc312f44ea09fb663aa6d95867772e412a6a23f1bc24183e54b32f134372c560f4b3fda19ba7a56b0f84fdcdecc22be6fd256639e918e019691c40a39aa5c9631820e42b28da61b8c75b45afae9d77d63ac8f4dda093762be4a890fbd86061dbd7e5e7c03c4dacde769e0f564df00403e449c0535537f1ba7263"; + + byte[] M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + assertTrue(Arrays.equals(M1, expected_M1)); + + boolean verified = client.verify(expected_M2); + assertTrue(verified); + + /* Test 2 */ + expected_A = trim(new BigInteger("180a1caf84efe93610a56772edea7b2d20ef3e9f34e578147b5402a898982f33131708233f9ddd2946246703c5db705f0859cca9cfc5b72ad5a05ec0c748545aa083d5b7b1bf06efe6737e9e0fd81b832b5cba983f1b9717041df8114385b93c8c669db06d62c5773b8e8a8f07e98a840a33d04d3448d4bcd2c042387c316750", 16).toByteArray()); + expected_M1 = trim(new BigInteger("a47782f23057a7e06704ea94389589b3c70971a63268acef2aefd74e234dd3c2", 16).toByteArray()); + a = "d89f0e33"; + expected_M2 = trim(new BigInteger("517278a03a0320a52dcb391caf5264d76149d7d9b71ed2b65536233344c550cf", 16).toByteArray()); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); + A = client.exponential(); + + B = "5f86fe2f7b7455e877e1760db8d3da1fcd4df0d10ec2a40298f87287bdb2f22c0ea54ff9b1f660cc1666459a7e2fd5501970b317490c3dfd3ba2e18f7be7526b72ea4d01e8f064754b935b107ced0892ce86112cbe32282f929907985fcb29f42c5d4dc32adeb29d12a611cac49cca3fefd2227efadc3989c2e72dd64a003141"; + M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + verified = client.verify(expected_M2); + assertTrue(verified); + + /* Test 3 */ + expected_A = trim(new BigInteger("a9c556c30bf4c1b1fdc1bc9e672ab4751806acc8581042b3779faaf25f85f47dfc58828742e2d2a06c51acbbb9f3fae0e01f64df0775a269f5ee4a6e71bc37b8a368e04b9053d399bc5b809ffd6ecab775a577804f2a5ed2e829f15e6af13bf0b78b6b108cf591bc9960992904fd1433698a51e0d05ee954cf98cbfe7995621e", 16).toByteArray()); + expected_M1 = trim(new BigInteger("0afca3583c4146990ec7312f9f4b4d9cceebc43a19f96709bf3d0a17b11dcc1e", 16).toByteArray()); + a = "50e662d6"; + expected_M2 = trim(new BigInteger("3bfb91c7d04b6da6381fe3d2648d992cdc6bc67b8ee16d1cfa733f786d492261", 16).toByteArray()); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + x = client.calculatePasswordHash(username, password, new BigInteger(salt, 16).toByteArray()); + A = client.exponential(); + + B = "6fe41e8262f4f8bc4ed9f4e1b4802ae3adac9c348e6efc07f16c6f5704b95a1f12325097489372c3936584a37301ebab400a32ac6699f4556da84f076489060527bd50578a317a3ec8b814bf2f4dd9c4adad368610eb638aa81663a205ba26d8f0b9654bf3940357b867cd42725e8532b97a2410a557d291aa55c0b44f249361"; + M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + verified = client.verify(expected_M2); + assertTrue(verified); + + /* Test 4: user abc, password abcdefghi */ + username = "abc"; + password = "abcdefghi"; + salt = "be26aac449a093e5"; + expected_A = trim(new BigInteger("c4013381bdb2fdd901944b9d823360f367c52635b576b9a50d2db77141d357ed391c3ac5fa452c2bbdc35f96bfed21df61627b40aed8f67f21ebf81e5621333f44049d6c9f6ad36464041438350e1f86000a8e3bfb63d4128c18322d2517b0d3ead63fd504a9c8f2156d46e64268110cec5f3ccab54a21559c7ab3ad67fedf90", 16).toByteArray()); + expected_x = trim(new BigInteger("6325967f1a161efd4e2d6e6fabbfccc32be05139cf82b08fb59c0a0db3f34bcf", 16).toByteArray()); + a = "5d4cde29"; + B = "e5d988752e8f265f01b98a1dcdecc4b685bd512e7cd9507f3c29f206c27dac91e027641eed1765c4603bbd7a9aa7fac300ef67dafe611ba2dbe29a32d83d486296f328d38b44c0c211d01d3fe422aac168b6850c87782338969c54594fc87804d4db34910ad4b5452a81027842ac8d8d8288fd44872e4c719ac8fb971d0a33e1"; + expected_M1 = trim(new BigInteger("e5972ddc53e6190735fc79cd823053a65ffb6041d69480adcba2f6a2dc2f2e86", 16).toByteArray()); + expected_M2 = trim(new BigInteger("8f4552b1021a4de621d8f50f0921c4d20651e702d9d71276f8f6c15b838de018", 16).toByteArray()); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + + x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); + assertTrue(Arrays.equals(x, expected_x)); + + A = client.exponential(); + assertTrue(Arrays.equals(A, expected_A)); + + M1 = client.response(trim(new BigInteger(salt, 16).toByteArray()), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + verified = client.verify(expected_M2); + assertTrue(verified); + + /* Test 5: user abc, password abcdefghi */ + username = "abc"; + password = "abcdefghi"; + salt = "be26aac449a093e5"; + expected_A = trim(new BigInteger("d13973fe4e0e13423cd036caf0912e23a1f9b0c23966f5a5897c8ff17c5cbac8bab7f07d9ac4ee47396a7c68e80ce854c84f243148521277900aaa132a7b93b61e54d742d7f36edb4cdef54bc78cca69ac72653a7ae0fc47ec1e9a84024ea9487a61357e28eddc185e4fe01388e64e6b8f688dd74471d56dd244204522e08483", 16).toByteArray()); + expected_x = trim(new BigInteger("6325967f1a161efd4e2d6e6fabbfccc32be05139cf82b08fb59c0a0db3f34bcf", 16).toByteArray()); + a = "fc57e4b1"; + B = "a6701686d9d987a43f06e8497330c8add8febd191a7a975bced0d058eb03ccc6805263349363b2d54ac435b01155dc41c6067287d9b93e3637ab3b7e8bc7d9cf38d9fdbb2ca9ee8ba1946a46cb555cb7dafcc177fcf7a4b0eb1e5db2249949c1fd15e0b7c1b3616f9e2649bdf074ed841efbdc9f29ee8c8bfcedeaed3dc49378"; + expected_M1 = trim(new BigInteger("0b590fde631566d0d3420a898a9b469656e64bfaff165c146b78964eee7920b8", 16).toByteArray()); + expected_M2 = trim(new BigInteger("04cf3ab3b75dbc4b116ca2fec949bf3deca1e360e016d7ab2b8a49904c534a27", 16).toByteArray()); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + + x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); + assertTrue(Arrays.equals(x, expected_x)); + + A = client.exponential(); + assertTrue(Arrays.equals(A, expected_A)); + + M1 = client.response(trim(new BigInteger(salt, 16).toByteArray()), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + verified = client.verify(expected_M2); + assertTrue(verified); + + /* Test 6: user and password of real use */ + username = "parmegv"; + password = "wR\"P}x@_,:k$`YYd*s T`-n."; + salt = "40c3f47b99ce8dc9"; + expected_A = trim(new BigInteger("490b5de7a287c59cefe267441a186ec24f63210fbf28877305f5896eaec5a7245d304ecb2b09d91066e627d7b2c8bf9e5271d882361a435355d1c2d1ac9d3069877189a01d64b2dd73a569e9e96b9a99767dbc02e04c839b09444f48430b113c1827c20b684ae33f5018051169f5acf4913ebd76a205c6f1aa2cc75747687d56", 16).toByteArray()); + String x_string = "9665839759b4fb9684e7438daecbd6e7129b4ebd3e4a107916e9a64bbbf399c9"; + expected_x = trim(new BigInteger(x_string, 16).toByteArray()); + assertEquals(new BigInteger(1, expected_x).toString(16), x_string); + a = "a72111a2"; + B = "6574ddce3e33c44a77198fa8b3656627e4a24c8786948e79f0c2588febaa485c94b1deb5e420bd3b46f9a34c7862525452ca7a0542c52f939d9f277a013aeceef7353a7741440f6dd2f6c2f1dc07fa5ca003e305c89c876a3035bd04f546b711d44da06a3ba827bc8affbf9ed46de1bfbc670ef9ed7c0bb8cdc588285d13849e"; + expected_M1 = trim(new BigInteger("03bbcf57aeaec89a3a254bb9650a924ea86aa0fdd83fd7274a75b7083f221cf0", 16).toByteArray()); + expected_M2 = trim(new BigInteger("082cf49ad5a34cc5ca571e3d063aec4bd96e7b96a6d951295180631650a84587", 16).toByteArray()); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + + x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); + assertTrue(Arrays.equals(x, expected_x)); + assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); + + A = client.exponential(); + assertTrue(Arrays.equals(A, expected_A)); + + M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + verified = client.verify(expected_M2); + assertTrue(verified); + + /* Test 7: password with ! */ + username = "parmegvtest3"; + password = "holahola!"; + salt = "1bf48d42b9a7ed32"; + expected_A = trim(new BigInteger("4e2fbe8db5d07f33ff1f4303959b5396dcffc1460b6ce8866fd388415f27fe10f9042986ab8682cdcf9a033e651bca89173688989adad854c91bc1918f98d5c82525fb6f328a8cf74ce1436b23821cba5337aaa20a3e5631e4b957053d542f2b5fc456e888371c9d6b94360b37adb2793eca8db100c24887c459e36d729a98e1", 16).toByteArray()); + x_string = "363d1d62dda07b2d987a9739ddb5ec32fcad9c7322fb64e87937f2da86c45d9f"; + expected_x = trim(new BigInteger(x_string, 16).toByteArray()); + assertEquals(new BigInteger(1, expected_x).toString(16), x_string); + a = "16dd0cf5"; + B = "dd5c9c5e13eb5daa6e7303928b3c826cec520ccef429c0dcb785be34c330d5bb89c99d7d94842b6b5c19cac600f884c50b26989b105f397115df7f3d13c5c7c2f6327cc547fc854ae40f09f1f6a104968bd510243feb104eb559e085fe1d720770be2887a1f424c534a3ab962d82e92458f652328bcf9878f95fdcf463d06193"; + expected_M1 = trim(new BigInteger("a7ffbff753a547b877f8944339b707b3ce1998da27badf253d56bf39f35308a6", 16).toByteArray()); + expected_M2 = trim(new BigInteger("5cc3d7f0077e978c83acdef14a725af01488c1728f0cf32cd7013d24faf5d901", 16).toByteArray()); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + + x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); + assertTrue(Arrays.equals(x, expected_x)); + assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); + + A = client.exponential(); + assertTrue(Arrays.equals(A, expected_A)); + + M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + verified = client.verify(expected_M2); + assertTrue(verified); + + /* Test 8: username and password *was* failing in localhost testProviderAPI*/ + username = "gg"; + password = "password"; + a = "bc925bfb"; + salt = "ff9ebb44e947cf59"; + expected_A = trim(new BigInteger("8f434633414faeaf035a0dea8c1cb7876bb1f8ee80d6fee8ea43ae60c4f9658550d825c25f1ed5c6a5543358bbcb559b76958c8047a2e7e5fe0072bc1f16401bcfa77b57651ff50dd665c6f28c302b37c98495eff397a56befead2e5ceffaace45f2ec200520258adb66df751e815e464656d869454e360d98cbc70f9c64fd4c", 16).toByteArray()); + x_string = "9cad2eca264380dd0b48e3b405e109c1be0615ee6ec92e7105eff5bc3a309fd9"; + expected_x = trim(new BigInteger(x_string, 16).toByteArray()); + assertEquals(new BigInteger(1, expected_x).toString(16), x_string); + B = "9ca2cd50b4c41047e4aa9e4fac9078ae21175e51e04a23877d6c2044765e39959e9a6a3ede99d08a556c196f51a2be12117681b1ef9d0b0498fb2fa4e88649ab9403e743504e3aaefbce8c5cb474eef8f4724ccd076fd33857de510d8509b67f166d986443bc262d776ec20985f617a7aa86e490290ce5d66332c8b45742a527"; + expected_M1 = trim(new BigInteger("7a2f768791abaeb954eb7f001bb60d91e6f61e959c8fcdefb58de857af9edaac", 16).toByteArray()); + expected_M2 = trim(new BigInteger("d78da7e0a23c9b87a2f09cdee05c510c105b4a8d471b47402c38f4cdfa49fe6d", 16).toByteArray()); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + + x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); + assertTrue(Arrays.equals(x, expected_x)); + assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); + + A = client.exponential(); + assertTrue(Arrays.equals(A, expected_A)); + + M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + verified = client.verify(expected_M2); + assertTrue(verified); + + /* Test 9: username and password *was* failing in localhost testProviderAPI*/ + username = "oo"; + password = "password"; + a = "1322ec50"; + salt = "a93c74934dcadd90"; + expected_A = trim(new BigInteger("c8e9f30a2f67977ee7e61e9ca5af8bd854b6cc98fe01dbe1b1a4cf002c1e2523b7e49f373a600ff85a84867817ec60fec532857812a07f5c6189f6172f133023af75ca4cb98b758bb84620d0aa3cfc74dc69e0507114c0aeab5a75c3ae3f07a919c5729420f03266c26ed41d1846e07de023ec68dd6830e9ebf129cf51abb571", 16).toByteArray()); + x_string = "20470538560c4beb4908e6bfe5b0e00da94223e361302a25c898cbdd3724020"; + expected_x = trim(new BigInteger(x_string, 16).toByteArray()); + assertEquals(new BigInteger(1, expected_x).toString(16), x_string); + B = "24f98ede155212bea8b1d8bacf8153735ee8114faa824c57c84df55f8d6072ab87f5ae885ce1062939dbaa68ca6e63147c1d2dc1f751e8be20d8a6f87287a2a83fcb1dc9b85dd406d438aeee5ccbc873603cb399627e26e6444e94b3d5d26764e866776c8960fe206bd33febeca9f55f6291dd2cb832eab69e5373f548adeefb"; + expected_M1 = trim(new BigInteger("1b35c705e563bd5239cdccc6627aa877c3023286f49b4b7c21341d2949ca2d15", 16).toByteArray()); + expected_M2 = trim(new BigInteger("a382025452bad8a6ccd0f703253fda90e7ea7bd0c2d466a389455080a4bd015d", 16).toByteArray()); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + + x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); + assertTrue(Arrays.equals(x, expected_x)); + assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); + + A = client.exponential(); + assertTrue(Arrays.equals(A, expected_A)); + + M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + verified = client.verify(expected_M2); + assertTrue(verified); + + /* Test 10: derived from test 11, discovered that password bytes should be in ISO-8859-1*/ + username = "nostradamus"; + password = "$[[//jjiilajfewahug43a89y¿"; + a = "800f0819"; + salt = "cfb9ae3ec5433076"; + expected_A = trim(new BigInteger("2ab09ee2fa01058f2f72fd2142b129f2ec26313801052889bcc4af57ee2e4d5b92c90cdfd6ecd660e82c635b2a091ba1b164e5b371c911ce0c4e69686baa120c58e2e0af84b2adc10da6cdfb0b579a1685032c57fd6ed1306d9713a562eddf5c833725042e825fa1abc7017f74760cb53d8c755ffe628c510022c296d1cd3584", 16).toByteArray()); + x_string = "9736a5e386a18f35bb08cac0f7c70bdbe120f2efe019874d0eb23b85b1955858"; + expected_x = trim(new BigInteger(x_string, 16).toByteArray()); + assertEquals(new BigInteger(1, expected_x).toString(16), x_string); + B = "2d19fe17dca1bda01044a0f406547895c32a10df2b0e69676de911273a8685d294763c4d16b3663f722b8980126e2c659efd33ffc6435a9594a2539e726c48e365893b3374670bd1958c13f55c2defa8ea9c0f9ba1345a5dca0e78debba434c8b755353d066d42bc5dfe0403fdcacfe5efd25c685f883ee6766c710b775c50f2"; + expected_M1 = trim(new BigInteger("a33feada1771c6f53e9343f5b9e69d51d4f15043c95fb663b6dd5b1c7af6f66b", 16).toByteArray()); + expected_M2 = trim(new BigInteger("9e99f9adfbfaa7add3626ed6e6aea94c9fa60dab6b8d56ad0cc950548f577d32", 16).toByteArray()); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + + x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); + assertTrue(Arrays.equals(x, expected_x)); + assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); + + A = client.exponential(); + assertTrue(Arrays.equals(A, expected_A)); + + M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + verified = client.verify(expected_M2); + assertTrue(verified); + + /* Test 11: username and password failing in localhost testProviderAPI*/ + username = "nostradamus"; + password = "$[['//\"jjiilajfewahug43a89y¿"; + a = "5bfbc968"; + salt = "1bcae1065951bbf5"; + expected_A = trim(new BigInteger("7a74c254d46dd6010a7090e574817a03f32ba13f98ed3c695d96f09c9d334e591771541400e68b6d27a19e734baccf3965ca79c0294ffbf553716b41fbca627c7cd3ea4a0d1c640c22411881696f59ad7ed8ce6ef7010e43f57fb3858aa4c3479dd41e4073afadb6a516c41f649b8cf30dea6366efa711c5106c83ea71b00da4", 16).toByteArray()); + x_string = "9834210874c883db35785ee6648079e13d22450c472d6469192ea775ff50c646"; + expected_x = trim(new BigInteger(x_string, 16).toByteArray()); + assertEquals(new BigInteger(1, expected_x).toString(16), x_string); + B = "285b00c034da5676dd8938ce6a7b717968fef2e5f479ecca6d95828a6ce809dd37893752c956245b5d13315987c50e57cc68aa4f770ff9ce977ddfd65052f278b90545286cf32b3d18307140514e0fe2269fc0437fb16104358f6fa127dc97281a017582759644862d736f48025f2b35cb1662067c11f2fcf0753e2f72c9e028"; + expected_M1 = trim(new BigInteger("fedbaff9d9a19efc4eea949b045297a6a3121cf371e2acdda85a2a1ca61c929d", 16).toByteArray()); + expected_M2 = trim(new BigInteger("ffccafa0febc1771a428082b30b7ce409856de4581c7d7d986f5b80015aba0d3", 16).toByteArray()); + + a_byte = new BigInteger(a, 16).toByteArray(); + params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); + client = new LeapSRPSession(username, password, params, a_byte); + + x = client.calculatePasswordHash(username, password, trim(new BigInteger(salt, 16).toByteArray())); + assertTrue(Arrays.equals(x, expected_x)); + assertEquals(new BigInteger(1, expected_x).toString(16), new BigInteger(1, x).toString(16)); + + A = client.exponential(); + assertTrue(Arrays.equals(A, expected_A)); + + M1 = client.response(new BigInteger(salt, 16).toByteArray(), new BigInteger(B, 16).toByteArray()); + + assertTrue(Arrays.equals(M1, expected_M1)); + + verified = client.verify(expected_M2); + assertTrue(verified); + } + + public 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; + } + +} diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..4c648a5f --- /dev/null +++ b/app/src/debug/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/app/src/debug/assets/urls/cdev.bitmask.net.url b/app/src/debug/assets/urls/cdev.bitmask.net.url new file mode 100644 index 00000000..4ceca5ee --- /dev/null +++ b/app/src/debug/assets/urls/cdev.bitmask.net.url @@ -0,0 +1,3 @@ +{ + "main_url" : "https://cdev.bitmask.net/" +} \ No newline at end of file diff --git a/app/src/debug/assets/urls/dev.bitmask.net.url b/app/src/debug/assets/urls/dev.bitmask.net.url new file mode 100644 index 00000000..a1e53f69 --- /dev/null +++ b/app/src/debug/assets/urls/dev.bitmask.net.url @@ -0,0 +1,3 @@ +{ + "main_url" : "https://dev.bitmask.net/" +} \ No newline at end of file diff --git a/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java b/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java new file mode 100644 index 00000000..f0aac40b --- /dev/null +++ b/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java @@ -0,0 +1,581 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + + + + + + +import android.app.Activity; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.Display; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View.MeasureSpec; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; +import org.json.JSONException; +import org.json.JSONObject; +import se.leap.bitmaskclient.DownloadFailedDialog.DownloadFailedDialogInterface; +import se.leap.bitmaskclient.NewProviderDialog.NewProviderDialogInterface; +import se.leap.bitmaskclient.ProviderAPIResultReceiver.Receiver; +import se.leap.bitmaskclient.ProviderDetailFragment.ProviderDetailFragmentInterface; +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; +import se.leap.bitmaskclient.R; + +/** + * Activity that builds and shows the list of known available providers. + * + * It also allows the user to enter custom providers with a button. + * + * @author parmegv + * + */ +public class ConfigurationWizard extends Activity +implements ProviderListFragment.Callbacks, NewProviderDialogInterface, ProviderDetailFragmentInterface, DownloadFailedDialogInterface, Receiver { + + private ProgressBar mProgressBar; + private TextView progressbar_description; + private ProviderListFragment provider_list_fragment; + private Intent mConfigState = new Intent(); + + final public static String TAG = "se.leap.bitmaskclient.ConfigurationWizard"; + final public static String TYPE_OF_CERTIFICATE = "type_of_certificate"; + final public static String ANON_CERTIFICATE = "anon_certificate"; + final public static String AUTHED_CERTIFICATE = "authed_certificate"; + + final protected static String PROVIDER_SET = "PROVIDER SET"; + final protected static String SERVICES_RETRIEVED = "SERVICES RETRIEVED"; + + public ProviderAPIResultReceiver providerAPI_result_receiver; + private ProviderAPIBroadcastReceiver_Update providerAPI_broadcast_receiver_update; + + private static SharedPreferences preferences; + private static boolean setting_up_provider = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + preferences = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE); + + setContentView(R.layout.configuration_wizard_activity); + mProgressBar = (ProgressBar) findViewById(R.id.progressbar_configuration_wizard); + mProgressBar.setVisibility(ProgressBar.INVISIBLE); + progressbar_description = (TextView) findViewById(R.id.progressbar_description); + progressbar_description.setVisibility(TextView.INVISIBLE); + providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); + providerAPI_result_receiver.setReceiver(this); + providerAPI_broadcast_receiver_update = new ProviderAPIBroadcastReceiver_Update(); + IntentFilter update_intent_filter = new IntentFilter(ProviderAPI.UPDATE_PROGRESSBAR); + update_intent_filter.addCategory(Intent.CATEGORY_DEFAULT); + registerReceiver(providerAPI_broadcast_receiver_update, update_intent_filter); + + loadPreseededProviders(); + + // Only create our fragments if we're not restoring a saved instance + if ( savedInstanceState == null ){ + // TODO Some welcome screen? + // We will need better flow control when we have more Fragments (e.g. user auth) + provider_list_fragment = ProviderListFragment.newInstance(); + Bundle arguments = new Bundle(); + int configuration_wizard_request_code = getIntent().getIntExtra(Dashboard.REQUEST_CODE, -1); + if(configuration_wizard_request_code == Dashboard.SWITCH_PROVIDER) { + arguments.putBoolean(ProviderListFragment.SHOW_ALL_PROVIDERS, true); + } + provider_list_fragment.setArguments(arguments); + + FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.beginTransaction() + .replace(R.id.configuration_wizard_layout, provider_list_fragment, ProviderListFragment.TAG) + .commit(); + } + + // TODO: If exposing deep links into your app, handle intents here. + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(providerAPI_broadcast_receiver_update); + } + + public void refreshProviderList(int top_padding) { + ProviderListFragment new_provider_list_fragment = new ProviderListFragment(); + Bundle top_padding_bundle = new Bundle(); + top_padding_bundle.putInt(ProviderListFragment.TOP_PADDING, top_padding); + new_provider_list_fragment.setArguments(top_padding_bundle); + + FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.beginTransaction() + .replace(R.id.configuration_wizard_layout, new_provider_list_fragment, ProviderListFragment.TAG) + .commit(); + } + + @Override + public void onReceiveResult(int resultCode, Bundle resultData) { + if(resultCode == ProviderAPI.PROVIDER_OK) { + mConfigState.setAction(PROVIDER_SET); + + if (preferences.getBoolean(EIP.ALLOWED_ANON, false)){ + mConfigState.putExtra(SERVICES_RETRIEVED, true); + downloadAnonCert(); + } else { + mProgressBar.incrementProgressBy(1); + mProgressBar.setVisibility(ProgressBar.GONE); + progressbar_description.setVisibility(TextView.GONE); + setResult(RESULT_OK); + showProviderDetails(getCurrentFocus()); + } + } else if(resultCode == ProviderAPI.PROVIDER_NOK) { + //refreshProviderList(0); + String reason_to_fail = resultData.getString(ProviderAPI.ERRORS); + showDownloadFailedDialog(getCurrentFocus(), reason_to_fail); + mProgressBar.setVisibility(ProgressBar.GONE); + progressbar_description.setVisibility(TextView.GONE); + preferences.edit().remove(Provider.KEY).commit(); + setting_up_provider = false; + setResult(RESULT_CANCELED, mConfigState); + } + else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { + mProgressBar.incrementProgressBy(1); + mProgressBar.setVisibility(ProgressBar.GONE); + progressbar_description.setVisibility(TextView.GONE); + //refreshProviderList(0); + setResult(RESULT_OK); + showProviderDetails(getCurrentFocus()); + } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { + //refreshProviderList(0); + mProgressBar.setVisibility(ProgressBar.GONE); + progressbar_description.setVisibility(TextView.GONE); + //Toast.makeText(getApplicationContext(), R.string.incorrectly_downloaded_certificate_message, Toast.LENGTH_LONG).show(); + setResult(RESULT_CANCELED, mConfigState); + } else if(resultCode == AboutActivity.VIEWED) { + // Do nothing, right now + // I need this for CW to wait for the About activity to end before going back to Dashboard. + } + } + + /** + * Callback method from {@link ProviderListFragment.Callbacks} + * indicating that the item with the given ID was selected. + */ + @Override + public void onItemSelected(String id) { + //TODO Code 2 pane view + // resetOldConnection(); + ProviderItem selected_provider = getProvider(id); + int provider_index = getProviderIndex(id); + + + startProgressBar(provider_index+1); + provider_list_fragment.hideAllBut(provider_index); + + boolean danger_on = true; + if(preferences.contains(ProviderItem.DANGER_ON)) + danger_on = preferences.getBoolean(ProviderItem.DANGER_ON, false); + setUpProvider(selected_provider.providerMainUrl(), danger_on); + } + + @Override + public void onBackPressed() { + if(setting_up_provider) { + stopSettingUpProvider(); + } else { + usualBackButton(); + } + } + + private void stopSettingUpProvider() { + ProviderAPI.stop(); + mProgressBar.setVisibility(ProgressBar.GONE); + mProgressBar.setProgress(0); + progressbar_description.setVisibility(TextView.GONE); + getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit().remove(Provider.KEY).commit(); + setting_up_provider = false; + showAllProviders(); + } + + private void usualBackButton() { + try { + boolean is_provider_set_up = new JSONObject(preferences.getString(Provider.KEY, "no provider")) != null ? true : false; + boolean is_provider_set_up_truly = new JSONObject(preferences.getString(Provider.KEY, "no provider")).length() != 0 ? true : false; + if(!is_provider_set_up || !is_provider_set_up_truly) { + askDashboardToQuitApp(); + } else { + setResult(RESULT_OK); + } + } catch (JSONException e) { + askDashboardToQuitApp(); + super.onBackPressed(); + e.printStackTrace(); + } + super.onBackPressed(); + } + private void askDashboardToQuitApp() { + Intent ask_quit = new Intent(); + ask_quit.putExtra(Dashboard.ACTION_QUIT, Dashboard.ACTION_QUIT); + setResult(RESULT_CANCELED, ask_quit); + } + + private ProviderItem getProvider(String name) { + Iterator providers_iterator = ProviderListContent.ITEMS.iterator(); + while(providers_iterator.hasNext()) { + ProviderItem provider = providers_iterator.next(); + if(provider.name().equalsIgnoreCase(name)) { + return provider; + } + } + return null; + } + + private String getId(String provider_main_url_string) { + try { + URL provider_url = new URL(provider_main_url_string); + URL aux_provider_url; + Iterator providers_iterator = ProviderListContent.ITEMS.iterator(); + while(providers_iterator.hasNext()) { + ProviderItem provider = providers_iterator.next(); + aux_provider_url = new URL(provider.providerMainUrl()); + if(isSameURL(provider_url, aux_provider_url)) { + return provider.name(); + } + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return ""; + } + + /** + * Checks, whether 2 urls are pointing to the same location. + * + * @param url a url + * @param baseUrl an other url, that should be compared. + * @return true, if the urls point to the same host and port and use the + * same protocol, false otherwise. + */ + private boolean isSameURL(final URL url, final URL baseUrl) { + if (!url.getProtocol().equals(baseUrl.getProtocol())) { + return false; + } + if (!url.getHost().equals(baseUrl.getHost())) { + return false; + } + if (url.getPort() != baseUrl.getPort()) { + return false; + } + return true; + } + + private void startProgressBar() { + mProgressBar.setVisibility(ProgressBar.VISIBLE); + mProgressBar.setProgress(0); + mProgressBar.setMax(3); + } + + private void startProgressBar(int list_item_index) { + mProgressBar.setVisibility(ProgressBar.VISIBLE); + progressbar_description.setVisibility(TextView.VISIBLE); + mProgressBar.setProgress(0); + mProgressBar.setMax(3); + int measured_height = listItemHeight(list_item_index); + mProgressBar.setTranslationY(measured_height); + progressbar_description.setTranslationY(measured_height + mProgressBar.getHeight()); + } + + private int getProviderIndex(String id) { + int index = 0; + Iterator providers_iterator = ProviderListContent.ITEMS.iterator(); + while(providers_iterator.hasNext()) { + ProviderItem provider = providers_iterator.next(); + if(provider.name().equalsIgnoreCase(id)) { + break; + } else index++; + } + return index; + } + + private int listItemHeight(int list_item_index) { + ListView provider_list_view = (ListView)findViewById(android.R.id.list); + ListAdapter provider_list_adapter = provider_list_view.getAdapter(); + View listItem = provider_list_adapter.getView(0, null, provider_list_view); + listItem.setLayoutParams(new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT)); + WindowManager wm = (WindowManager) getApplicationContext() + .getSystemService(Context.WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + int screenWidth = display.getWidth(); // deprecated + + int listViewWidth = screenWidth - 10 - 10; + int widthSpec = MeasureSpec.makeMeasureSpec(listViewWidth, + MeasureSpec.AT_MOST); + listItem.measure(widthSpec, 0); + + return listItem.getMeasuredHeight(); +} + + /** + * Loads providers data from url file contained in the project + * @return true if the file was read correctly + */ + private boolean loadPreseededProviders() { + boolean loaded_preseeded_providers = false; + AssetManager asset_manager = getAssets(); + String[] urls_filepaths = null; + try { + String url_files_folder = "urls"; + //TODO Put that folder in a better place (also inside the "for") + urls_filepaths = asset_manager.list(url_files_folder); + String provider_name = ""; + for(String url_filepath : urls_filepaths) + { + boolean custom = false; + provider_name = url_filepath.subSequence(0, url_filepath.indexOf(".")).toString(); + if(ProviderListContent.ITEMS.isEmpty()) //TODO I have to implement a way of checking if a provider new or is already present in that ITEMS list + ProviderListContent.addItem(new ProviderItem(provider_name, asset_manager.open(url_files_folder + "/" + url_filepath))); + loaded_preseeded_providers = true; + } + } catch (IOException e) { + loaded_preseeded_providers = false; + } + + return loaded_preseeded_providers; + } + + /** + * Asks ProviderAPI to download an anonymous (anon) VPN certificate. + */ + private void downloadAnonCert() { + Intent provider_API_command = new Intent(this, ProviderAPI.class); + + Bundle parameters = new Bundle(); + + parameters.putString(TYPE_OF_CERTIFICATE, ANON_CERTIFICATE); + + provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + + startService(provider_API_command); + } + + /** + * Open the new provider dialog + */ + public void addAndSelectNewProvider() { + FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); + Fragment previous_new_provider_dialog = getFragmentManager().findFragmentByTag(NewProviderDialog.TAG); + if (previous_new_provider_dialog != null) { + fragment_transaction.remove(previous_new_provider_dialog); + } + fragment_transaction.addToBackStack(null); + + DialogFragment newFragment = NewProviderDialog.newInstance(); + newFragment.show(fragment_transaction, NewProviderDialog.TAG); + } + + /** + * Open the new provider dialog with data + */ + public void addAndSelectNewProvider(String main_url, boolean danger_on) { + FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); + Fragment previous_new_provider_dialog = getFragmentManager().findFragmentByTag(NewProviderDialog.TAG); + if (previous_new_provider_dialog != null) { + fragment_transaction.remove(previous_new_provider_dialog); + } + + DialogFragment newFragment = NewProviderDialog.newInstance(); + Bundle data = new Bundle(); + data.putString(Provider.MAIN_URL, main_url); + data.putBoolean(ProviderItem.DANGER_ON, danger_on); + newFragment.setArguments(data); + newFragment.show(fragment_transaction, NewProviderDialog.TAG); + } + + /** + * Once selected a provider, this fragment offers the user to log in, + * use it anonymously (if possible) + * or cancel his/her election pressing the back button. + * @param view + * @param reason_to_fail + */ + public void showDownloadFailedDialog(View view, String reason_to_fail) { + FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); + Fragment previous_provider_details_dialog = getFragmentManager().findFragmentByTag(DownloadFailedDialog.TAG); + if (previous_provider_details_dialog != null) { + fragment_transaction.remove(previous_provider_details_dialog); + } + fragment_transaction.addToBackStack(null); + + DialogFragment newFragment = DownloadFailedDialog.newInstance(reason_to_fail); + newFragment.show(fragment_transaction, DownloadFailedDialog.TAG); + } + + /** + * Once selected a provider, this fragment offers the user to log in, + * use it anonymously (if possible) + * or cancel his/her election pressing the back button. + * @param view + */ + public void showProviderDetails(View view) { + if(setting_up_provider) { + FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); + Fragment previous_provider_details_dialog = getFragmentManager().findFragmentByTag(ProviderDetailFragment.TAG); + if (previous_provider_details_dialog != null) { + fragment_transaction.remove(previous_provider_details_dialog); + } + fragment_transaction.addToBackStack(null); + + DialogFragment newFragment = ProviderDetailFragment.newInstance(); + newFragment.show(fragment_transaction, ProviderDetailFragment.TAG); + } + } + + public void showAndSelectProvider(String provider_main_url, boolean danger_on) { + if(getId(provider_main_url).isEmpty()) + showProvider(provider_main_url, danger_on); + autoSelectProvider(provider_main_url, danger_on); + } + + private void showProvider(final String provider_main_url, final boolean danger_on) { + String provider_name = provider_main_url.replaceFirst("http[s]?://", "").replaceFirst("\\/", "_"); + ProviderItem added_provider = new ProviderItem(provider_name, provider_main_url); + provider_list_fragment.addItem(added_provider); + } + + private void autoSelectProvider(String provider_main_url, boolean danger_on) { + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(ProviderItem.DANGER_ON, danger_on).commit(); + onItemSelected(getId(provider_main_url)); + } + + /** + * Asks ProviderAPI to download a new provider.json file + * @param provider_name + * @param provider_main_url + * @param danger_on tells if HTTPS client should bypass certificate errors + */ + public void setUpProvider(String provider_main_url, boolean danger_on) { + Intent provider_API_command = new Intent(this, ProviderAPI.class); + Bundle parameters = new Bundle(); + parameters.putString(Provider.MAIN_URL, provider_main_url); + parameters.putBoolean(ProviderItem.DANGER_ON, danger_on); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + + startService(provider_API_command); + setting_up_provider = true; + } + + public void retrySetUpProvider() { + cancelSettingUpProvider(); + if(!ProviderAPI.caCertDownloaded()) { + addAndSelectNewProvider(ProviderAPI.lastProviderMainUrl(), ProviderAPI.lastDangerOn()); + } else { + Intent provider_API_command = new Intent(this, ProviderAPI.class); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + + startService(provider_API_command); + } + } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.configuration_wizard_activity, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + switch (item.getItemId()){ + case R.id.about_leap: + startActivityForResult(new Intent(this, AboutActivity.class), 0); + return true; + case R.id.new_provider: + addAndSelectNewProvider(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + public void showAllProviders() { + provider_list_fragment = (ProviderListFragment) getFragmentManager().findFragmentByTag(ProviderListFragment.TAG); + if(provider_list_fragment != null) + provider_list_fragment.unhideAll(); + } + + public void cancelSettingUpProvider() { + provider_list_fragment = (ProviderListFragment) getFragmentManager().findFragmentByTag(ProviderListFragment.TAG); + if(provider_list_fragment != null && preferences.contains(ProviderItem.DANGER_ON)) { + provider_list_fragment.removeLastItem(); + } + preferences.edit().remove(Provider.KEY).remove(ProviderItem.DANGER_ON).remove(EIP.ALLOWED_ANON).remove(EIP.KEY).commit(); + } + + @Override + public void login() { + Intent ask_login = new Intent(); + ask_login.putExtra(LogInDialog.VERB, LogInDialog.VERB); + setResult(RESULT_OK, ask_login); + setting_up_provider = false; + finish(); + } + + @Override + public void use_anonymously() { + setResult(RESULT_OK); + setting_up_provider = false; + finish(); + } + + public class ProviderAPIBroadcastReceiver_Update extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + int update = intent.getIntExtra(ProviderAPI.CURRENT_PROGRESS, 0); + mProgressBar.setProgress(update); + } + } +} diff --git a/app/src/debug/java/se/leap/bitmaskclient/NewProviderDialog.java b/app/src/debug/java/se/leap/bitmaskclient/NewProviderDialog.java new file mode 100644 index 00000000..cf09c64b --- /dev/null +++ b/app/src/debug/java/se/leap/bitmaskclient/NewProviderDialog.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; +import se.leap.bitmaskclient.R; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Toast; + +/** + * Implements the new custom provider dialog. + * + * @author parmegv + * + */ +public class NewProviderDialog extends DialogFragment { + + final public static String TAG = "newProviderDialog"; + + public interface NewProviderDialogInterface { + public void showAndSelectProvider(String url_provider, boolean danger_on); + } + + NewProviderDialogInterface interface_with_ConfigurationWizard; + + /** + * @return a new instance of this DialogFragment. + */ + public static DialogFragment newInstance() { + NewProviderDialog dialog_fragment = new NewProviderDialog(); + return dialog_fragment; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + interface_with_ConfigurationWizard = (NewProviderDialogInterface) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement NoticeDialogListener"); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + LayoutInflater inflater = getActivity().getLayoutInflater(); + View new_provider_dialog_view = inflater.inflate(R.layout.new_provider_dialog, null); + final EditText url_input_field = (EditText)new_provider_dialog_view.findViewById(R.id.new_provider_url); + if(getArguments() != null && getArguments().containsKey(Provider.MAIN_URL)) { + url_input_field.setText(getArguments().getString(Provider.MAIN_URL)); + } + final CheckBox danger_checkbox = (CheckBox)new_provider_dialog_view.findViewById(R.id.danger_checkbox); + if(getArguments() != null && getArguments().containsKey(ProviderItem.DANGER_ON)) { + danger_checkbox.setActivated(getArguments().getBoolean(ProviderItem.DANGER_ON)); + } + + builder.setView(new_provider_dialog_view) + .setMessage(R.string.introduce_new_provider) + .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + String entered_url = url_input_field.getText().toString().trim(); + if(!entered_url.startsWith("https://")) { + if (entered_url.startsWith("http://")){ + entered_url = entered_url.substring("http://".length()); + } + entered_url = "https://".concat(entered_url); + } + boolean danger_on = danger_checkbox.isChecked(); + if(validURL(entered_url)) { + interface_with_ConfigurationWizard.showAndSelectProvider(entered_url, danger_on); + Toast.makeText(getActivity().getApplicationContext(), R.string.valid_url_entered, Toast.LENGTH_LONG).show(); + } else { + url_input_field.setText(""); + danger_checkbox.setChecked(false); + Toast.makeText(getActivity().getApplicationContext(), R.string.not_valid_url_entered, Toast.LENGTH_LONG).show();; + } + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + // Create the AlertDialog object and return it + return builder.create(); + } + + /** + * Checks if the entered url is valid or not. + * @param entered_url + * @return true if it's not empty nor contains only the protocol. + */ + boolean validURL(String entered_url) { + //return !entered_url.isEmpty() && entered_url.matches("http[s]?://.+") && !entered_url.replaceFirst("http[s]?://", "").isEmpty(); + return android.util.Patterns.WEB_URL.matcher(entered_url).matches(); + } +} diff --git a/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java new file mode 100644 index 00000000..75ef511d --- /dev/null +++ b/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java @@ -0,0 +1,875 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import java.io.DataOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Scanner; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import org.apache.http.client.ClientProtocolException; +import org.jboss.security.srp.SRPParameters; +import org.json.JSONException; +import org.json.JSONObject; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; +import android.app.IntentService; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; +import android.util.Base64; +import android.util.Log; + + +/** + * Implements HTTP api methods used to manage communications with the provider server. + * + * It's an IntentService because it downloads data from the Internet, so it operates in the background. + * + * @author parmegv + * @author MeanderingCode + * + */ +public class ProviderAPI extends IntentService { + + private Handler mHandler; + + final public static String + SET_UP_PROVIDER = "setUpProvider", + DOWNLOAD_NEW_PROVIDER_DOTJSON = "downloadNewProviderDotJSON", + SRP_REGISTER = "srpRegister", + SRP_AUTH = "srpAuth", + LOG_OUT = "logOut", + DOWNLOAD_CERTIFICATE = "downloadUserAuthedCertificate", + PARAMETERS = "parameters", + RESULT_KEY = "result", + RECEIVER_KEY = "receiver", + SESSION_ID_COOKIE_KEY = "session_id_cookie_key", + SESSION_ID_KEY = "session_id", + ERRORS = "errors", + UPDATE_PROGRESSBAR = "update_progressbar", + CURRENT_PROGRESS = "current_progress", + TAG = "provider_api_tag" + ; + + final public static int + CUSTOM_PROVIDER_ADDED = 0, + SRP_AUTHENTICATION_SUCCESSFUL = 3, + SRP_AUTHENTICATION_FAILED = 4, + SRP_REGISTRATION_SUCCESSFUL = 5, + SRP_REGISTRATION_FAILED = 6, + LOGOUT_SUCCESSFUL = 7, + LOGOUT_FAILED = 8, + CORRECTLY_DOWNLOADED_CERTIFICATE = 9, + INCORRECTLY_DOWNLOADED_CERTIFICATE = 10, + PROVIDER_OK = 11, + PROVIDER_NOK = 12, + CORRECTLY_DOWNLOADED_ANON_CERTIFICATE = 13, + INCORRECTLY_DOWNLOADED_ANON_CERTIFICATE = 14 + ; + + private static boolean + CA_CERT_DOWNLOADED = false, + PROVIDER_JSON_DOWNLOADED = false, + EIP_SERVICE_JSON_DOWNLOADED = false + ; + + private static String last_provider_main_url; + private static boolean last_danger_on = false; + private static boolean setting_up_provider = true; + + public static void stop() { + setting_up_provider = false; + } + + public ProviderAPI() { + super("ProviderAPI"); + Log.v("ClassName", "Provider API"); + } + + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(); + CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER) ); + } + + public static String lastProviderMainUrl() { + return last_provider_main_url; + } + + public static boolean lastDangerOn() { + return last_danger_on; + } + + private String formatErrorMessage(final int toast_string_id) { + return "{ \"" + ERRORS + "\" : \""+getResources().getString(toast_string_id)+"\" }"; + } + + @Override + protected void onHandleIntent(Intent command) { + final ResultReceiver receiver = command.getParcelableExtra(RECEIVER_KEY); + String action = command.getAction(); + Bundle parameters = command.getBundleExtra(PARAMETERS); + setting_up_provider = true; + + if(action.equalsIgnoreCase(SET_UP_PROVIDER)) { + Bundle result = setUpProvider(parameters); + if(setting_up_provider) { + if(result.getBoolean(RESULT_KEY)) { + receiver.send(PROVIDER_OK, result); + } else { + receiver.send(PROVIDER_NOK, result); + } + } + } else if (action.equalsIgnoreCase(SRP_AUTH)) { + Bundle session_id_bundle = authenticateBySRP(parameters); + if(session_id_bundle.getBoolean(RESULT_KEY)) { + receiver.send(SRP_AUTHENTICATION_SUCCESSFUL, session_id_bundle); + } else { + receiver.send(SRP_AUTHENTICATION_FAILED, session_id_bundle); + } + } else if (action.equalsIgnoreCase(LOG_OUT)) { + if(logOut(parameters)) { + receiver.send(LOGOUT_SUCCESSFUL, Bundle.EMPTY); + } else { + receiver.send(LOGOUT_FAILED, Bundle.EMPTY); + } + } else if (action.equalsIgnoreCase(DOWNLOAD_CERTIFICATE)) { + if(getNewCert(parameters)) { + receiver.send(CORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); + } else { + receiver.send(INCORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); + } + } + } + + /** + * Starts the authentication process using SRP protocol. + * + * @param task containing: username, password and api url. + * @return a bundle with a boolean value mapped to a key named RESULT_KEY, and which is true if authentication was successful. + */ + private Bundle authenticateBySRP(Bundle task) { + Bundle session_id_bundle = new Bundle(); + int progress = 0; + + String username = (String) task.get(LogInDialog.USERNAME); + String password = (String) task.get(LogInDialog.PASSWORD); + if(validUserLoginData(username, password)) { + + String authentication_server = (String) task.get(Provider.API_URL); + + SRPParameters params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), ConfigHelper.G.toByteArray(), BigInteger.ZERO.toByteArray(), "SHA-256"); + LeapSRPSession client = new LeapSRPSession(username, password, params); + byte[] A = client.exponential(); + broadcast_progress(progress++); + try { + JSONObject saltAndB = sendAToSRPServer(authentication_server, username, new BigInteger(1, A).toString(16)); + if(saltAndB.length() > 0) { + String salt = saltAndB.getString(LeapSRPSession.SALT); + broadcast_progress(progress++); + byte[] Bbytes = new BigInteger(saltAndB.getString("B"), 16).toByteArray(); + byte[] M1 = client.response(new BigInteger(salt, 16).toByteArray(), Bbytes); + if(M1 != null) { + broadcast_progress(progress++); + JSONObject session_idAndM2 = sendM1ToSRPServer(authentication_server, username, M1); + if(session_idAndM2.has(LeapSRPSession.M2) && client.verify((byte[])session_idAndM2.get(LeapSRPSession.M2))) { + session_id_bundle.putBoolean(RESULT_KEY, true); + broadcast_progress(progress++); + } else { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_bad_user_password_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + } + } else { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(LogInDialog.USERNAME, username); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_srp_math_error_user_message)); + } + broadcast_progress(progress++); + } else { + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_bad_user_password_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + session_id_bundle.putBoolean(RESULT_KEY, false); + } + } catch (ClientProtocolException e) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_client_http_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + } catch (IOException e) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_io_exception_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + } catch (JSONException e) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_json_exception_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + } catch (NoSuchAlgorithmException e) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_no_such_algorithm_exception_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + } catch (KeyManagementException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (KeyStoreException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (CertificateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } else { + if(!wellFormedPassword(password)) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(LogInDialog.USERNAME, username); + session_id_bundle.putBoolean(LogInDialog.PASSWORD_INVALID_LENGTH, true); + } + if(username.isEmpty()) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putBoolean(LogInDialog.USERNAME_MISSING, true); + } + } + + return session_id_bundle; + } + + /** + * Sets up an intent with the progress value passed as a parameter + * and sends it as a broadcast. + * @param progress + */ + private void broadcast_progress(int progress) { + Intent intentUpdate = new Intent(); + intentUpdate.setAction(UPDATE_PROGRESSBAR); + intentUpdate.addCategory(Intent.CATEGORY_DEFAULT); + intentUpdate.putExtra(CURRENT_PROGRESS, progress); + sendBroadcast(intentUpdate); + } + + /** + * Validates parameters entered by the user to log in + * @param entered_username + * @param entered_password + * @return true if both parameters are present and the entered password length is greater or equal to eight (8). + */ + private boolean validUserLoginData(String entered_username, String entered_password) { + return !(entered_username.isEmpty()) && wellFormedPassword(entered_password); + } + + /** + * Validates a password + * @param entered_password + * @return true if the entered password length is greater or equal to eight (8). + */ + private boolean wellFormedPassword(String entered_password) { + return entered_password.length() >= 8; + } + + /** + * Sends an HTTP POST request to the authentication server with the SRP Parameter A. + * @param server_url + * @param username + * @param clientA First SRP parameter sent + * @return response from authentication server + * @throws ClientProtocolException + * @throws IOException + * @throws JSONException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws KeyManagementException + */ + private JSONObject sendAToSRPServer(String server_url, String username, String clientA) throws ClientProtocolException, IOException, JSONException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException { + Map parameters = new HashMap(); + parameters.put("login", username); + parameters.put("A", clientA); + return sendToServer(server_url + "/sessions.json", "POST", parameters); + + /*HttpPost post = new HttpPost(server_url + "/sessions.json" + "?" + "login=" + username + "&&" + "A=" + clientA); + return sendToServer(post);*/ + } + + /** + * Sends an HTTP PUT request to the authentication server with the SRP Parameter M1 (or simply M). + * @param server_url + * @param username + * @param m1 Second SRP parameter sent + * @return response from authentication server + * @throws ClientProtocolException + * @throws IOException + * @throws JSONException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws KeyManagementException + */ + private JSONObject sendM1ToSRPServer(String server_url, String username, byte[] m1) throws ClientProtocolException, IOException, JSONException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException { + Map parameters = new HashMap(); + parameters.put("client_auth", new BigInteger(1, ConfigHelper.trim(m1)).toString(16)); + + //HttpPut put = new HttpPut(server_url + "/sessions/" + username +".json" + "?" + "client_auth" + "=" + new BigInteger(1, ConfigHelper.trim(m1)).toString(16)); + JSONObject json_response = sendToServer(server_url + "/sessions/" + username +".json", "PUT", parameters); + + JSONObject session_idAndM2 = new JSONObject(); + if(json_response.length() > 0) { + byte[] M2_not_trimmed = new BigInteger(json_response.getString(LeapSRPSession.M2), 16).toByteArray(); + /*Cookie session_id_cookie = LeapHttpClient.getInstance(getApplicationContext()).getCookieStore().getCookies().get(0); + session_idAndM2.put(ConfigHelper.SESSION_ID_COOKIE_KEY, session_id_cookie.getName()); + session_idAndM2.put(ConfigHelper.SESSION_ID_KEY, session_id_cookie.getValue());*/ + session_idAndM2.put(LeapSRPSession.M2, ConfigHelper.trim(M2_not_trimmed)); + CookieHandler.setDefault(null); // we don't need cookies anymore + String token = json_response.getString(LeapSRPSession.TOKEN); + LeapSRPSession.setToken(token); + } + return session_idAndM2; + } + + /** + * Executes an HTTP request expecting a JSON response. + * @param url + * @param request_method + * @param parameters + * @return response from authentication server + * @throws IOException + * @throws JSONException + * @throws MalformedURLException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws KeyManagementException + */ + private JSONObject sendToServer(String url, String request_method, Map parameters) throws JSONException, MalformedURLException, IOException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException { + JSONObject json_response; + InputStream is = null; + HttpsURLConnection urlConnection = (HttpsURLConnection)new URL(url).openConnection(); + urlConnection.setRequestMethod(request_method); + urlConnection.setChunkedStreamingMode(0); + urlConnection.setSSLSocketFactory(getProviderSSLSocketFactory()); + try { + + DataOutputStream writer = new DataOutputStream(urlConnection.getOutputStream()); + writer.writeBytes(formatHttpParameters(parameters)); + writer.close(); + + is = urlConnection.getInputStream(); + String plain_response = new Scanner(is).useDelimiter("\\A").next(); + json_response = new JSONObject(plain_response); + } finally { + InputStream error_stream = urlConnection.getErrorStream(); + if(error_stream != null) { + String error_response = new Scanner(error_stream).useDelimiter("\\A").next(); + urlConnection.disconnect(); + Log.d("Error", error_response); + json_response = new JSONObject(error_response); + if(!json_response.isNull(ERRORS) || json_response.has(ERRORS)) { + return new JSONObject(); + } + } + } + + return json_response; + } + + private String formatHttpParameters(Map parameters) throws UnsupportedEncodingException { + StringBuilder result = new StringBuilder(); + boolean first = true; + + Iterator parameter_iterator = parameters.keySet().iterator(); + while(parameter_iterator.hasNext()) { + if(first) + first = false; + else + result.append("&&"); + + String key = parameter_iterator.next(); + String value = parameters.get(key); + + result.append(URLEncoder.encode(key, "UTF-8")); + result.append("="); + result.append(URLEncoder.encode(value, "UTF-8")); + } + + return result.toString(); + } + + + + + /** + * Downloads a provider.json from a given URL, adding a new provider using the given name. + * @param task containing a boolean meaning if the provider is custom or not, another boolean meaning if the user completely trusts this provider, the provider name and its provider.json url. + * @return a bundle with a boolean value mapped to a key named RESULT_KEY, and which is true if the update was successful. + */ + private Bundle setUpProvider(Bundle task) { + int progress = 0; + Bundle current_download = new Bundle(); + + if(task != null && task.containsKey(ProviderItem.DANGER_ON) && task.containsKey(Provider.MAIN_URL)) { + last_danger_on = task.getBoolean(ProviderItem.DANGER_ON); + last_provider_main_url = task.getString(Provider.MAIN_URL); + CA_CERT_DOWNLOADED = PROVIDER_JSON_DOWNLOADED = EIP_SERVICE_JSON_DOWNLOADED = false; + } + + if(!CA_CERT_DOWNLOADED) + current_download = downloadCACert(last_provider_main_url, last_danger_on); + if(CA_CERT_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) { + broadcast_progress(progress++); + CA_CERT_DOWNLOADED = true; + if(!PROVIDER_JSON_DOWNLOADED) + current_download = getAndSetProviderJson(last_provider_main_url); + if(PROVIDER_JSON_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) { + broadcast_progress(progress++); + PROVIDER_JSON_DOWNLOADED = true; + current_download = getAndSetEipServiceJson(); + if(current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY)) { + broadcast_progress(progress++); + EIP_SERVICE_JSON_DOWNLOADED = true; + } + } + } + + return current_download; + } + + private Bundle downloadCACert(String provider_main_url, boolean danger_on) { + Bundle result = new Bundle(); + String cert_string = downloadWithCommercialCA(provider_main_url + "/ca.crt", danger_on); + + if(validCertificate(cert_string) && setting_up_provider) { + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(Provider.CA_CERT, cert_string).commit(); + result.putBoolean(RESULT_KEY, true); + } else { + String reason_to_fail = pickErrorMessage(cert_string); + result.putString(ERRORS, reason_to_fail); + result.putBoolean(RESULT_KEY, false); + } + + return result; + } + + + public static boolean caCertDownloaded() { + return CA_CERT_DOWNLOADED; + } + + private boolean validCertificate(String cert_string) { + boolean result = false; + if(!ConfigHelper.checkErroneousDownload(cert_string)) { + X509Certificate certCert = ConfigHelper.parseX509CertificateFromString(cert_string); + try { + Base64.encodeToString( certCert.getEncoded(), Base64.DEFAULT); + result = true; + } catch (CertificateEncodingException e) { + Log.d(TAG, e.getLocalizedMessage()); + } + } + + return result; + } + + private Bundle getAndSetProviderJson(String provider_main_url) { + Bundle result = new Bundle(); + + if(setting_up_provider) { + String provider_dot_json_string = downloadWithProviderCA(provider_main_url + "/provider.json", true); + + try { + JSONObject provider_json = new JSONObject(provider_dot_json_string); + String name = provider_json.getString(Provider.NAME); + //TODO setProviderName(name); + + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(Provider.KEY, provider_json.toString()).commit(); + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(EIP.ALLOWED_ANON, provider_json.getJSONObject(Provider.SERVICE).getBoolean(EIP.ALLOWED_ANON)).commit(); + + result.putBoolean(RESULT_KEY, true); + } catch (JSONException e) { + //TODO Error message should be contained in that provider_dot_json_string + String reason_to_fail = pickErrorMessage(provider_dot_json_string); + result.putString(ERRORS, reason_to_fail); + result.putBoolean(RESULT_KEY, false); + } + } + return result; + } + + + + public static boolean providerJsonDownloaded() { + return PROVIDER_JSON_DOWNLOADED; + } + + private Bundle getAndSetEipServiceJson() { + Bundle result = new Bundle(); + String eip_service_json_string = ""; + if(setting_up_provider) { + try { + JSONObject provider_json = new JSONObject(getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.KEY, "")); + String eip_service_url = provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION) + "/" + EIP.SERVICE_API_PATH; + eip_service_json_string = downloadWithProviderCA(eip_service_url, true); + JSONObject eip_service_json = new JSONObject(eip_service_json_string); + eip_service_json.getInt(Provider.API_RETURN_SERIAL); + + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(EIP.KEY, eip_service_json.toString()).commit(); + + result.putBoolean(RESULT_KEY, true); + } catch (JSONException e) { + String reason_to_fail = pickErrorMessage(eip_service_json_string); + result.putString(ERRORS, reason_to_fail); + result.putBoolean(RESULT_KEY, false); + } + } + return result; + } + + public static boolean eipServiceDownloaded() { + return EIP_SERVICE_JSON_DOWNLOADED; + } + + /** + * Interprets the error message as a JSON object and extract the "errors" keyword pair. + * If the error message is not a JSON object, then it is returned untouched. + * @param string_json_error_message + * @return final error message + */ + private String pickErrorMessage(String string_json_error_message) { + String error_message = ""; + try { + JSONObject json_error_message = new JSONObject(string_json_error_message); + error_message = json_error_message.getString(ERRORS); + } catch (JSONException e) { + // TODO Auto-generated catch block + error_message = string_json_error_message; + } + + return error_message; + } + + /** + * Tries to download the contents of the provided url using commercially validated CA certificate from chosen provider. + * + * If danger_on flag is true, SSL exceptions will be managed by futher methods that will try to use some bypass methods. + * @param string_url + * @param danger_on if the user completely trusts this provider + * @return + */ + private String downloadWithCommercialCA(String string_url, boolean danger_on) { + + String json_file_content = ""; + + URL provider_url = null; + int seconds_of_timeout = 1; + try { + provider_url = new URL(string_url); + URLConnection url_connection = provider_url.openConnection(); + url_connection.setConnectTimeout(seconds_of_timeout*1000); + if(!LeapSRPSession.getToken().isEmpty()) + url_connection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token = " + LeapSRPSession.getToken()); + json_file_content = new Scanner(url_connection.getInputStream()).useDelimiter("\\A").next(); + } catch (MalformedURLException e) { + json_file_content = formatErrorMessage(R.string.malformed_url); + } catch(SocketTimeoutException e) { + json_file_content = formatErrorMessage(R.string.server_unreachable_message); + } catch (IOException e) { + if(provider_url != null) { + json_file_content = downloadWithProviderCA(string_url, danger_on); + } else { + json_file_content = formatErrorMessage(R.string.certificate_error); + } + } catch (Exception e) { + if(provider_url != null && danger_on) { + json_file_content = downloadWithProviderCA(string_url, danger_on); + } + } + + return json_file_content; + } + + /** + * Tries to download the contents of the provided url using not commercially validated CA certificate from chosen provider. + * @param url as a string + * @param danger_on true to download CA certificate in case it has not been downloaded. + * @return an empty string if it fails, the url content if not. + */ + private String downloadWithProviderCA(String url_string, boolean danger_on) { + String json_file_content = ""; + + try { + URL url = new URL(url_string); + // Tell the URLConnection to use a SocketFactory from our SSLContext + HttpsURLConnection urlConnection = + (HttpsURLConnection)url.openConnection(); + urlConnection.setSSLSocketFactory(getProviderSSLSocketFactory()); + if(!LeapSRPSession.getToken().isEmpty()) + urlConnection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token=" + LeapSRPSession.getToken()); + json_file_content = new Scanner(urlConnection.getInputStream()).useDelimiter("\\A").next(); + } catch (CertificateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (UnknownHostException e) { + json_file_content = formatErrorMessage(R.string.server_unreachable_message); + } catch (IOException e) { + // The downloaded certificate doesn't validate our https connection. + if(danger_on) { + json_file_content = downloadWithoutCA(url_string); + } else { + json_file_content = formatErrorMessage(R.string.certificate_error); + } + } catch (KeyStoreException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (KeyManagementException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return json_file_content; + } + + private javax.net.ssl.SSLSocketFactory getProviderSSLSocketFactory() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException { + String provider_cert_string = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.CA_CERT,""); + + java.security.cert.Certificate provider_certificate = ConfigHelper.parseX509CertificateFromString(provider_cert_string); + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("provider_ca_certificate", provider_certificate); + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + + // Create an SSLContext that uses our TrustManager + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + + return context.getSocketFactory(); + } + + /** + * Downloads the string that's in the url with any certificate. + */ + private String downloadWithoutCA(String url_string) { + String string = ""; + try { + + HostnameVerifier hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + class DefaultTrustManager implements X509TrustManager { + + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} + + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + } + + SSLContext context = SSLContext.getInstance("TLS"); + context.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom()); + + URL url = new URL(url_string); + HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); + urlConnection.setSSLSocketFactory(context.getSocketFactory()); + urlConnection.setHostnameVerifier(hostnameVerifier); + string = new Scanner(urlConnection.getInputStream()).useDelimiter("\\A").next(); + System.out.println("String ignoring certificate = " + string); + } catch (FileNotFoundException e) { + e.printStackTrace(); + string = formatErrorMessage(R.string.server_unreachable_message); + } catch (IOException e) { + // The downloaded certificate doesn't validate our https connection. + e.printStackTrace(); + string = formatErrorMessage(R.string.certificate_error); + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (KeyManagementException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return string; + } + + /** + * Logs out from the api url retrieved from the task. + * @param task containing api url from which the user will log out + * @return true if there were no exceptions + */ + private boolean logOut(Bundle task) { + try { + String delete_url = task.getString(Provider.API_URL) + "/logout"; + int progress = 0; + + HttpsURLConnection urlConnection = (HttpsURLConnection)new URL(delete_url).openConnection(); + urlConnection.setRequestMethod("DELETE"); + urlConnection.setSSLSocketFactory(getProviderSSLSocketFactory()); + + int responseCode = urlConnection.getResponseCode(); + broadcast_progress(progress++); + LeapSRPSession.setToken(""); + Log.d(TAG, Integer.toString(responseCode)); + } catch (ClientProtocolException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } catch (IndexOutOfBoundsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } catch (KeyManagementException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } 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(); + } + return true; + } + + /** + * Downloads a new OpenVPN certificate, attaching authenticated cookie for authenticated certificate. + * + * @param task containing the type of the certificate to be downloaded + * @return true if certificate was downloaded correctly, false if provider.json or danger_on flag are not present in SharedPreferences, or if the certificate url could not be parsed as a URI, or if there was an SSL error. + */ + private boolean getNewCert(Bundle task) { + + try { + String type_of_certificate = task.getString(ConfigurationWizard.TYPE_OF_CERTIFICATE); + JSONObject provider_json = new JSONObject(getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.KEY, "")); + + String provider_main_url = provider_json.getString(Provider.API_URL); + URL new_cert_string_url = new URL(provider_main_url + "/" + provider_json.getString(Provider.API_VERSION) + "/" + EIP.CERTIFICATE); + + boolean danger_on = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getBoolean(ProviderItem.DANGER_ON, false); + + String cert_string = downloadWithProviderCA(new_cert_string_url.toString(), danger_on); + + if(!cert_string.isEmpty()) { + if(ConfigHelper.checkErroneousDownload(cert_string)) { + String reason_to_fail = provider_json.getString(ERRORS); + //result.putString(ConfigHelper.ERRORS_KEY, reason_to_fail); + //result.putBoolean(ConfigHelper.RESULT_KEY, false); + return false; + } else { + + // API returns concatenated cert & key. Split them for OpenVPN options + String certificateString = null, keyString = null; + String[] certAndKey = cert_string.split("(?<=-\n)"); + for (int i=0; i < certAndKey.length-1; i++){ + if ( certAndKey[i].contains("KEY") ) { + keyString = certAndKey[i++] + certAndKey[i]; + } + else if ( certAndKey[i].contains("CERTIFICATE") ) { + certificateString = certAndKey[i++] + certAndKey[i]; + } + } + try { + RSAPrivateKey keyCert = ConfigHelper.parseRsaKeyFromString(keyString); + keyString = Base64.encodeToString( keyCert.getEncoded(), Base64.DEFAULT ); + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(EIP.PRIVATE_KEY, "-----BEGIN RSA PRIVATE KEY-----\n"+keyString+"-----END RSA PRIVATE KEY-----").commit(); + + X509Certificate certCert = ConfigHelper.parseX509CertificateFromString(certificateString); + certificateString = Base64.encodeToString( certCert.getEncoded(), Base64.DEFAULT); + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(EIP.CERTIFICATE, "-----BEGIN CERTIFICATE-----\n"+certificateString+"-----END CERTIFICATE-----").commit(); + + return true; + } catch (CertificateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } + } + } else { + return false; + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } /*catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + }*/ + } +} diff --git a/app/src/debug/java/se/leap/bitmaskclient/ProviderDetailFragment.java b/app/src/debug/java/se/leap/bitmaskclient/ProviderDetailFragment.java new file mode 100644 index 00000000..c067ce2b --- /dev/null +++ b/app/src/debug/java/se/leap/bitmaskclient/ProviderDetailFragment.java @@ -0,0 +1,115 @@ +package se.leap.bitmaskclient; + +import org.json.JSONException; +import org.json.JSONObject; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +public class ProviderDetailFragment extends DialogFragment { + + final public static String TAG = "providerDetailFragment"; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + try { + + LayoutInflater inflater = getActivity().getLayoutInflater(); + View provider_detail_view = inflater.inflate(R.layout.provider_detail_fragment, null); + + JSONObject provider_json = new JSONObject(getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, getActivity().MODE_PRIVATE).getString(Provider.KEY, "")); + + final TextView domain = (TextView)provider_detail_view.findViewById(R.id.provider_detail_domain); + domain.setText(provider_json.getString(Provider.DOMAIN)); + final TextView name = (TextView)provider_detail_view.findViewById(R.id.provider_detail_name); + name.setText(provider_json.getJSONObject(Provider.NAME).getString("en")); + final TextView description = (TextView)provider_detail_view.findViewById(R.id.provider_detail_description); + description.setText(provider_json.getJSONObject(Provider.DESCRIPTION).getString("en")); + + builder.setView(provider_detail_view); + builder.setTitle(R.string.provider_details_fragment_title); + + if(anon_allowed(provider_json)) { + builder.setPositiveButton(R.string.use_anonymously_button, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + interface_with_configuration_wizard.use_anonymously(); + } + }); + } + + if(registration_allowed(provider_json)) { + builder.setNegativeButton(R.string.login_button, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + interface_with_configuration_wizard.login(); + } + }); + } + + return builder.create(); + } catch (JSONException e) { + return null; + } + } + + private boolean anon_allowed(JSONObject provider_json) { + try { + JSONObject service_description = provider_json.getJSONObject(Provider.SERVICE); + return service_description.has(EIP.ALLOWED_ANON) && service_description.getBoolean(EIP.ALLOWED_ANON); + } catch (JSONException e) { + return false; + } + } + + private boolean registration_allowed(JSONObject provider_json) { + try { + JSONObject service_description = provider_json.getJSONObject(Provider.SERVICE); + return service_description.has(Provider.ALLOW_REGISTRATION) && service_description.getBoolean(Provider.ALLOW_REGISTRATION); + } catch (JSONException e) { + return false; + } + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + SharedPreferences.Editor editor = getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit(); + editor.remove(Provider.KEY).remove(ProviderItem.DANGER_ON).remove(EIP.ALLOWED_ANON).remove(EIP.KEY).commit(); + interface_with_configuration_wizard.showAllProviders(); + } + + public static DialogFragment newInstance() { + ProviderDetailFragment provider_detail_fragment = new ProviderDetailFragment(); + return provider_detail_fragment; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + interface_with_configuration_wizard = (ProviderDetailFragmentInterface) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement LogInDialogListener"); + } + } + + public interface ProviderDetailFragmentInterface { + public void login(); + public void use_anonymously(); + public void showAllProviders(); + } + + ProviderDetailFragmentInterface interface_with_configuration_wizard; +} diff --git a/app/src/debug/java/se/leap/bitmaskclient/ProviderListContent.java b/app/src/debug/java/se/leap/bitmaskclient/ProviderListContent.java new file mode 100644 index 00000000..e1ca4f9a --- /dev/null +++ b/app/src/debug/java/se/leap/bitmaskclient/ProviderListContent.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.net.URL; +import java.net.MalformedURLException; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Models the provider list shown in the ConfigurationWizard. + * + * @author parmegv + * + */ +public class ProviderListContent { + + public static List ITEMS = new ArrayList(); + + public static Map ITEM_MAP = new HashMap(); + + /** + * Adds a new provider item to the end of the items map, and to the items list. + * @param item + */ + public static void addItem(ProviderItem item) { + ITEMS.add(item); + ITEM_MAP.put(String.valueOf(ITEMS.size()), item); + } + public static void removeItem(ProviderItem item) { + ITEMS.remove(item); + ITEM_MAP.remove(item); + } + + /** + * A provider item. + */ + public static class ProviderItem { + final public static String CUSTOM = "custom"; + final public static String DANGER_ON = "danger_on"; + private String provider_main_url; + private String name; + + /** + * @param name of the provider + * @param urls_file_input_stream file input stream linking with the assets url file + * @param custom if it's a new provider entered by the user or not + * @param danger_on if the user trusts completely the new provider + */ + public ProviderItem(String name, InputStream urls_file_input_stream) { + + try { + byte[] urls_file_bytes = new byte[urls_file_input_stream.available()]; + urls_file_input_stream.read(urls_file_bytes); + String urls_file_content = new String(urls_file_bytes); + JSONObject file_contents = new JSONObject(urls_file_content); + provider_main_url = file_contents.getString(Provider.MAIN_URL); + this.name = name; + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * @param name of the provider + * @param provider_main_url used to download provider.json file of the provider + * @param provider_json already downloaded + * @param custom if it's a new provider entered by the user or not + */ + public ProviderItem(String name, String provider_main_url) { + this.name = name; + this.provider_main_url = provider_main_url; + } + + public String name() { return name; } + + public String providerMainUrl() { return provider_main_url; } + + public String domain() { + try { + return new URL(provider_main_url).getHost(); + } catch (MalformedURLException e) { + return provider_main_url.replaceFirst("http[s]?://", "").replaceFirst("/.*", ""); + } + } + } +} diff --git a/app/src/debug/res/layout-xlarge/new_provider_dialog.xml b/app/src/debug/res/layout-xlarge/new_provider_dialog.xml new file mode 100644 index 00000000..fc7d84ab --- /dev/null +++ b/app/src/debug/res/layout-xlarge/new_provider_dialog.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/debug/res/layout/new_provider_dialog.xml b/app/src/debug/res/layout/new_provider_dialog.xml new file mode 100644 index 00000000..19b8f442 --- /dev/null +++ b/app/src/debug/res/layout/new_provider_dialog.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml new file mode 100644 index 00000000..3e568115 --- /dev/null +++ b/app/src/debug/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Skip security check + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b2366b82 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/minivpn.armeabi b/app/src/main/assets/minivpn.armeabi new file mode 100644 index 00000000..7018dbc9 Binary files /dev/null and b/app/src/main/assets/minivpn.armeabi differ diff --git a/app/src/main/assets/minivpn.armeabi-v7a b/app/src/main/assets/minivpn.armeabi-v7a new file mode 100644 index 00000000..a8e01017 Binary files /dev/null and b/app/src/main/assets/minivpn.armeabi-v7a differ diff --git a/app/src/main/assets/minivpn.mips b/app/src/main/assets/minivpn.mips new file mode 100644 index 00000000..c44e56c5 Binary files /dev/null and b/app/src/main/assets/minivpn.mips differ diff --git a/app/src/main/assets/minivpn.x86 b/app/src/main/assets/minivpn.x86 new file mode 100644 index 00000000..1a6bf464 Binary files /dev/null and b/app/src/main/assets/minivpn.x86 differ diff --git a/app/src/main/assets/urls/bitmask demo.url b/app/src/main/assets/urls/bitmask demo.url new file mode 100644 index 00000000..1a412055 --- /dev/null +++ b/app/src/main/assets/urls/bitmask demo.url @@ -0,0 +1,3 @@ +{ + "main_url" : "https://demo.bitmask.net/" +} diff --git a/app/src/main/java/org/jboss/security/srp/SRPParameters.java b/app/src/main/java/org/jboss/security/srp/SRPParameters.java new file mode 100644 index 00000000..4b188cb3 --- /dev/null +++ b/app/src/main/java/org/jboss/security/srp/SRPParameters.java @@ -0,0 +1,150 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2006, Red Hat Middleware LLC, and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.security.srp; + +import java.io.Serializable; +import java.util.Arrays; + +import org.spongycastle.util.encoders.Base64; + +/** The RFC2945 algorithm session parameters that the client and server +agree to use. In addition to the base RFC2945 parameters, one can choose an +alternate hash algorithm for the private session key. + +@author Scott.Stark@jboss.org +@version $Revision: 57210 $ +*/ +public class SRPParameters implements Cloneable, Serializable +{ + /** The serial version ID. + * @since 1.2.4.1 + */ + private static final long serialVersionUID = 6438772808805276693L; + + /** The algorithm safe-prime modulus */ + public final byte[] N; + /** The algorithm primitive generator */ + public final byte[] g; + /** The random password salt originally used to verify the password */ + public final byte[] s; + /** The algorithm to hash the session key to produce K. To be consistent + with the RFC2945 description this must be SHA_Interleave as implemented + by the JBossSX security provider. For compatibility with earlier JBossSX + SRP releases the algorithm must be SHA_ReverseInterleave. This name is + passed to java.security.MessageDigest.getInstance(). */ + public final String hashAlgorithm; + /** The algorithm to use for any encryption of data. + */ + public final String cipherAlgorithm; + /** The cipher intialization vector bytes + */ + public byte[] cipherIV; + + /** Creates a new instance of SRPParameters */ + public SRPParameters(byte[] N, byte[] g, byte[] s) + { + this(N, g, s, "SHA_Interleave", null); + } + public SRPParameters(byte[] N, byte[] g, byte[] s, String hashAlgorithm) + { + this(N, g, s, hashAlgorithm, null); + } + public SRPParameters(byte[] N, byte[] g, byte[] s, String hashAlgorithm, + String cipherAlgorithm) + { + this(N, g, s, hashAlgorithm, cipherAlgorithm, null); + } + public SRPParameters(byte[] N, byte[] g, byte[] s, String hashAlgorithm, + String cipherAlgorithm, byte[] cipherIV) + { + this.N = N; + this.g = g; + this.s = s; + if( hashAlgorithm == null ) + hashAlgorithm = "SHA_Interleave"; + this.hashAlgorithm = hashAlgorithm; + this.cipherAlgorithm = cipherAlgorithm; + this.cipherIV = cipherIV; + } + + public Object clone() + { + Object clone = null; + try + { + clone = super.clone(); + } + catch(CloneNotSupportedException e) + { + } + return clone; + } + + public int hashCode() + { + int hashCode = hashAlgorithm.hashCode(); + for(int i = 0; i < N.length; i ++) + hashCode += N[i]; + for(int i = 0; i < g.length; i ++) + hashCode += g[i]; + for(int i = 0; i < s.length; i ++) + hashCode += s[i]; + return hashCode; + } + + public boolean equals(Object obj) + { + boolean equals = false; + if( obj instanceof SRPParameters ) + { + SRPParameters p = (SRPParameters) obj; + equals = hashAlgorithm.equals(p.hashAlgorithm); + if( equals == true ) + equals = Arrays.equals(N, p.N); + if( equals == true ) + equals = Arrays.equals(g, p.g); + if( equals == true ) + equals = Arrays.equals(s, p.s); + } + return equals; + } + + public String toString() + { + StringBuffer tmp = new StringBuffer(super.toString()); + tmp.append('{'); + tmp.append("N: "); + tmp.append(Base64.encode(N)); + tmp.append("|g: "); + tmp.append(Base64.encode(g)); + tmp.append("|s: "); + tmp.append(Base64.encode(s)); + tmp.append("|hashAlgorithm: "); + tmp.append(hashAlgorithm); + tmp.append("|cipherAlgorithm: "); + tmp.append(cipherAlgorithm); + tmp.append("|cipherIV: "); + tmp.append(cipherIV); + tmp.append('}'); + return tmp.toString(); + } +} diff --git a/app/src/main/java/org/spongycastle/util/encoders/Base64.java b/app/src/main/java/org/spongycastle/util/encoders/Base64.java new file mode 100644 index 00000000..87bd80a0 --- /dev/null +++ b/app/src/main/java/org/spongycastle/util/encoders/Base64.java @@ -0,0 +1,121 @@ +package org.spongycastle.util.encoders; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class Base64 +{ + private static final Encoder encoder = new Base64Encoder(); + + /** + * encode the input data producing a base 64 encoded byte array. + * + * @return a byte array containing the base 64 encoded data. + */ + public static byte[] encode( + byte[] data) + { + int len = (data.length + 2) / 3 * 4; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); + + try + { + encoder.encode(data, 0, data.length, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception encoding base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * Encode the byte data to base 64 writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + OutputStream out) + throws IOException + { + return encoder.encode(data, 0, data.length, out); + } + + /** + * Encode the byte data to base 64 writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + return encoder.encode(data, off, length, out); + } + + /** + * decode the base 64 encoded input data. It is assumed the input data is valid. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + byte[] data) + { + int len = data.length / 4 * 3; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); + + try + { + encoder.decode(data, 0, data.length, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception decoding base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * decode the base 64 encoded String data - whitespace will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + String data) + { + int len = data.length() / 4 * 3; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); + + try + { + encoder.decode(data, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception decoding base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * decode the base 64 encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public static int decode( + String data, + OutputStream out) + throws IOException + { + return encoder.decode(data, out); + } +} diff --git a/app/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java b/app/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java new file mode 100644 index 00000000..84060707 --- /dev/null +++ b/app/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java @@ -0,0 +1,298 @@ +package org.spongycastle.util.encoders; + +import java.io.IOException; +import java.io.OutputStream; + +public class Base64Encoder + implements Encoder +{ + protected final byte[] encodingTable = + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', + (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', + (byte)'7', (byte)'8', (byte)'9', + (byte)'+', (byte)'/' + }; + + protected byte padding = (byte)'='; + + /* + * set up the decoding table. + */ + protected final byte[] decodingTable = new byte[128]; + + protected void initialiseDecodingTable() + { + for (int i = 0; i < encodingTable.length; i++) + { + decodingTable[encodingTable[i]] = (byte)i; + } + } + + public Base64Encoder() + { + initialiseDecodingTable(); + } + + /** + * encode the input data producing a base 64 output stream. + * + * @return the number of bytes produced. + */ + public int encode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + int modulus = length % 3; + int dataLength = (length - modulus); + int a1, a2, a3; + + for (int i = off; i < off + dataLength; i += 3) + { + a1 = data[i] & 0xff; + a2 = data[i + 1] & 0xff; + a3 = data[i + 2] & 0xff; + + out.write(encodingTable[(a1 >>> 2) & 0x3f]); + out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]); + out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]); + out.write(encodingTable[a3 & 0x3f]); + } + + /* + * process the tail end. + */ + int b1, b2, b3; + int d1, d2; + + switch (modulus) + { + case 0: /* nothing left to do */ + break; + case 1: + d1 = data[off + dataLength] & 0xff; + b1 = (d1 >>> 2) & 0x3f; + b2 = (d1 << 4) & 0x3f; + + out.write(encodingTable[b1]); + out.write(encodingTable[b2]); + out.write(padding); + out.write(padding); + break; + case 2: + d1 = data[off + dataLength] & 0xff; + d2 = data[off + dataLength + 1] & 0xff; + + b1 = (d1 >>> 2) & 0x3f; + b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; + b3 = (d2 << 2) & 0x3f; + + out.write(encodingTable[b1]); + out.write(encodingTable[b2]); + out.write(encodingTable[b3]); + out.write(padding); + break; + } + + return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4); + } + + private boolean ignore( + char c) + { + return (c == '\n' || c =='\r' || c == '\t' || c == ' '); + } + + /** + * decode the base 64 encoded byte data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public int decode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + byte b1, b2, b3, b4; + int outLen = 0; + + int end = off + length; + + while (end > off) + { + if (!ignore((char)data[end - 1])) + { + break; + } + + end--; + } + + int i = off; + int finish = end - 4; + + i = nextI(data, i, finish); + + while (i < finish) + { + b1 = decodingTable[data[i++]]; + + i = nextI(data, i, finish); + + b2 = decodingTable[data[i++]]; + + i = nextI(data, i, finish); + + b3 = decodingTable[data[i++]]; + + i = nextI(data, i, finish); + + b4 = decodingTable[data[i++]]; + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + out.write((b3 << 6) | b4); + + outLen += 3; + + i = nextI(data, i, finish); + } + + outLen += decodeLastBlock(out, (char)data[end - 4], (char)data[end - 3], (char)data[end - 2], (char)data[end - 1]); + + return outLen; + } + + private int nextI(byte[] data, int i, int finish) + { + while ((i < finish) && ignore((char)data[i])) + { + i++; + } + return i; + } + + /** + * decode the base 64 encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public int decode( + String data, + OutputStream out) + throws IOException + { + byte b1, b2, b3, b4; + int length = 0; + + int end = data.length(); + + while (end > 0) + { + if (!ignore(data.charAt(end - 1))) + { + break; + } + + end--; + } + + int i = 0; + int finish = end - 4; + + i = nextI(data, i, finish); + + while (i < finish) + { + b1 = decodingTable[data.charAt(i++)]; + + i = nextI(data, i, finish); + + b2 = decodingTable[data.charAt(i++)]; + + i = nextI(data, i, finish); + + b3 = decodingTable[data.charAt(i++)]; + + i = nextI(data, i, finish); + + b4 = decodingTable[data.charAt(i++)]; + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + out.write((b3 << 6) | b4); + + length += 3; + + i = nextI(data, i, finish); + } + + length += decodeLastBlock(out, data.charAt(end - 4), data.charAt(end - 3), data.charAt(end - 2), data.charAt(end - 1)); + + return length; + } + + private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4) + throws IOException + { + byte b1, b2, b3, b4; + + if (c3 == padding) + { + b1 = decodingTable[c1]; + b2 = decodingTable[c2]; + + out.write((b1 << 2) | (b2 >> 4)); + + return 1; + } + else if (c4 == padding) + { + b1 = decodingTable[c1]; + b2 = decodingTable[c2]; + b3 = decodingTable[c3]; + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + + return 2; + } + else + { + b1 = decodingTable[c1]; + b2 = decodingTable[c2]; + b3 = decodingTable[c3]; + b4 = decodingTable[c4]; + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + out.write((b3 << 6) | b4); + + return 3; + } + } + + private int nextI(String data, int i, int finish) + { + while ((i < finish) && ignore(data.charAt(i))) + { + i++; + } + return i; + } +} diff --git a/app/src/main/java/org/spongycastle/util/encoders/Encoder.java b/app/src/main/java/org/spongycastle/util/encoders/Encoder.java new file mode 100644 index 00000000..106c36b7 --- /dev/null +++ b/app/src/main/java/org/spongycastle/util/encoders/Encoder.java @@ -0,0 +1,17 @@ +package org.spongycastle.util.encoders; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Encode and decode byte arrays (typically from binary to 7-bit ASCII + * encodings). + */ +public interface Encoder +{ + int encode(byte[] data, int off, int length, OutputStream out) throws IOException; + + int decode(byte[] data, int off, int length, OutputStream out) throws IOException; + + int decode(String data, OutputStream out) throws IOException; +} diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java b/app/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java new file mode 100644 index 00000000..0127ca0c --- /dev/null +++ b/app/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java @@ -0,0 +1,26 @@ +package org.spongycastle.util.io.pem; + +import java.io.IOException; + +@SuppressWarnings("serial") +public class PemGenerationException + extends IOException +{ + private Throwable cause; + + public PemGenerationException(String message, Throwable cause) + { + super(message); + this.cause = cause; + } + + public PemGenerationException(String message) + { + super(message); + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemHeader.java b/app/src/main/java/org/spongycastle/util/io/pem/PemHeader.java new file mode 100644 index 00000000..4adb815e --- /dev/null +++ b/app/src/main/java/org/spongycastle/util/io/pem/PemHeader.java @@ -0,0 +1,66 @@ +package org.spongycastle.util.io.pem; + +public class PemHeader +{ + private String name; + private String value; + + public PemHeader(String name, String value) + { + this.name = name; + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + + public int hashCode() + { + return getHashCode(this.name) + 31 * getHashCode(this.value); + } + + public boolean equals(Object o) + { + if (!(o instanceof PemHeader)) + { + return false; + } + + PemHeader other = (PemHeader)o; + + return other == this || (isEqual(this.name, other.name) && isEqual(this.value, other.value)); + } + + private int getHashCode(String s) + { + if (s == null) + { + return 1; + } + + return s.hashCode(); + } + + private boolean isEqual(String s1, String s2) + { + if (s1 == s2) + { + return true; + } + + if (s1 == null || s2 == null) + { + return false; + } + + return s1.equals(s2); + } + +} diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemObject.java b/app/src/main/java/org/spongycastle/util/io/pem/PemObject.java new file mode 100644 index 00000000..6f7c79c5 --- /dev/null +++ b/app/src/main/java/org/spongycastle/util/io/pem/PemObject.java @@ -0,0 +1,62 @@ +package org.spongycastle.util.io.pem; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@SuppressWarnings("all") +public class PemObject + implements PemObjectGenerator +{ + private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList()); + + private String type; + private List headers; + private byte[] content; + + /** + * Generic constructor for object without headers. + * + * @param type pem object type. + * @param content the binary content of the object. + */ + public PemObject(String type, byte[] content) + { + this(type, EMPTY_LIST, content); + } + + /** + * Generic constructor for object with headers. + * + * @param type pem object type. + * @param headers a list of PemHeader objects. + * @param content the binary content of the object. + */ + public PemObject(String type, List headers, byte[] content) + { + this.type = type; + this.headers = Collections.unmodifiableList(headers); + this.content = content; + } + + public String getType() + { + return type; + } + + public List getHeaders() + { + return headers; + } + + public byte[] getContent() + { + return content; + } + + public PemObject generate() + throws PemGenerationException + { + return this; + } +} diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java b/app/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java new file mode 100644 index 00000000..1a8cea6d --- /dev/null +++ b/app/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java @@ -0,0 +1,7 @@ +package org.spongycastle.util.io.pem; + +public interface PemObjectGenerator +{ + PemObject generate() + throws PemGenerationException; +} diff --git a/app/src/main/java/org/spongycastle/util/io/pem/PemWriter.java b/app/src/main/java/org/spongycastle/util/io/pem/PemWriter.java new file mode 100644 index 00000000..f5a6a363 --- /dev/null +++ b/app/src/main/java/org/spongycastle/util/io/pem/PemWriter.java @@ -0,0 +1,138 @@ +package org.spongycastle.util.io.pem; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Iterator; + +import org.spongycastle.util.encoders.Base64; + +/** + * A generic PEM writer, based on RFC 1421 + */ +@SuppressWarnings("all") +public class PemWriter + extends BufferedWriter +{ + private static final int LINE_LENGTH = 64; + + private final int nlLength; + private char[] buf = new char[LINE_LENGTH]; + + /** + * Base constructor. + * + * @param out output stream to use. + */ + public PemWriter(Writer out) + { + super(out); + + String nl = System.getProperty("line.separator"); + if (nl != null) + { + nlLength = nl.length(); + } + else + { + nlLength = 2; + } + } + + /** + * Return the number of bytes or characters required to contain the + * passed in object if it is PEM encoded. + * + * @param obj pem object to be output + * @return an estimate of the number of bytes + */ + public int getOutputSize(PemObject obj) + { + // BEGIN and END boundaries. + int size = (2 * (obj.getType().length() + 10 + nlLength)) + 6 + 4; + + if (!obj.getHeaders().isEmpty()) + { + for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) + { + PemHeader hdr = (PemHeader)it.next(); + + size += hdr.getName().length() + ": ".length() + hdr.getValue().length() + nlLength; + } + + size += nlLength; + } + + // base64 encoding + int dataLen = ((obj.getContent().length + 2) / 3) * 4; + + size += dataLen + (((dataLen + LINE_LENGTH - 1) / LINE_LENGTH) * nlLength); + + return size; + } + + public void writeObject(PemObjectGenerator objGen) + throws IOException + { + PemObject obj = objGen.generate(); + + writePreEncapsulationBoundary(obj.getType()); + + if (!obj.getHeaders().isEmpty()) + { + for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) + { + PemHeader hdr = (PemHeader)it.next(); + + this.write(hdr.getName()); + this.write(": "); + this.write(hdr.getValue()); + this.newLine(); + } + + this.newLine(); + } + + writeEncoded(obj.getContent()); + writePostEncapsulationBoundary(obj.getType()); + } + + private void writeEncoded(byte[] bytes) + throws IOException + { + bytes = Base64.encode(bytes); + + for (int i = 0; i < bytes.length; i += buf.length) + { + int index = 0; + + while (index != buf.length) + { + if ((i + index) >= bytes.length) + { + break; + } + buf[index] = (char)bytes[i + index]; + index++; + } + this.write(buf, 0, index); + this.newLine(); + } + } + + private void writePreEncapsulationBoundary( + String type) + throws IOException + { + this.write("-----BEGIN " + type + "-----"); + this.newLine(); + } + + private void writePostEncapsulationBoundary( + String type) + throws IOException + { + this.write("-----END " + type + "-----"); + this.newLine(); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java b/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java new file mode 100644 index 00000000..6d025422 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java @@ -0,0 +1,40 @@ +package se.leap.bitmaskclient; + +import android.app.Activity; +import android.app.Fragment; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import se.leap.bitmaskclient.R; + +public class AboutActivity extends Activity { + + final public static String TAG = "aboutFragment"; + final public static int VIEWED = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.about); + TextView ver = (TextView) findViewById(R.id.version); + + String version; + String name="Openvpn"; + try { + PackageInfo packageinfo = getPackageManager().getPackageInfo(getPackageName(), 0); + version = packageinfo.versionName; + name = getString(R.string.app); + } catch (NameNotFoundException e) { + version = "error fetching version"; + } + + + ver.setText(getString(R.string.version_info,name,version)); + setResult(VIEWED); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java new file mode 100644 index 00000000..a8bd3b7a --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java @@ -0,0 +1,194 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.io.InputStream; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +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 org.json.JSONException; +import org.json.JSONObject; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Base64; + +/** + * Stores constants, and implements auxiliary methods used across all LEAP Android classes. + * + * @author parmegv + * @author MeanderingCode + * + */ +public class ConfigHelper { + 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 downloaded_string) { + try { + if(new JSONObject(downloaded_string).has(ProviderAPI.ERRORS) || downloaded_string.isEmpty()) { + return true; + } else { + return false; + } + } catch(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 certificate_string) { + java.security.cert.Certificate certificate = null; + CertificateFactory cf; + try { + cf = CertificateFactory.getInstance("X.509"); + + certificate_string = certificate_string.replaceFirst("-----BEGIN CERTIFICATE-----", "").replaceFirst("-----END CERTIFICATE-----", "").trim(); + byte[] cert_bytes = Base64.decode(certificate_string, Base64.DEFAULT); + InputStream caInput = new ByteArrayInputStream(cert_bytes); + try { + certificate = cf.generateCertificate(caInput); + System.out.println("ca=" + ((X509Certificate) certificate).getSubjectDN()); + } finally { + caInput.close(); + } + } catch (CertificateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + return null; + } + + return (X509Certificate) certificate; + } + + protected static RSAPrivateKey parseRsaKeyFromString(String RsaKeyString) { + RSAPrivateKey key = null; + 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, Base64.DEFAULT) ); + 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; + } + + return key; + } + + /** + * Adds a new X509 certificate given its input stream and its provider name + * @param provider used to store the certificate in the keystore + * @param inputStream from which X509 certificate must be generated. + */ + public static void addTrustedCertificate(String provider, InputStream inputStream) { + CertificateFactory cf; + try { + cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = + (X509Certificate)cf.generateCertificate(inputStream); + keystore_trusted.setCertificateEntry(provider, cert); + } catch (CertificateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (KeyStoreException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * Adds a new X509 certificate given in its string from and using its provider name + * @param provider used to store the certificate in the keystore + * @param certificate + */ + public static void addTrustedCertificate(String provider, String certificate) { + + try { + X509Certificate cert = ConfigHelper.parseX509CertificateFromString(certificate); + if(keystore_trusted == null) { + keystore_trusted = KeyStore.getInstance("BKS"); + keystore_trusted.load(null); + } + keystore_trusted.setCertificateEntry(provider, cert); + } catch (KeyStoreException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (CertificateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * @return class wide keystore + */ + public static KeyStore getKeystore() { + return keystore_trusted; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java new file mode 100644 index 00000000..b388b84a --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -0,0 +1,479 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import org.json.JSONException; +import org.json.JSONObject; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.ProviderAPIResultReceiver.Receiver; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +/** + * The main user facing Activity of LEAP Android, consisting of status, controls, + * and access to preferences. + * + * @author Sean Leonard + * @author parmegv + */ +public class Dashboard extends Activity implements LogInDialog.LogInDialogInterface,Receiver { + + protected static final int CONFIGURE_LEAP = 0; + protected static final int SWITCH_PROVIDER = 1; + + final public static String SHARED_PREFERENCES = "LEAPPreferences"; + final public static String ACTION_QUIT = "quit"; + public static final String REQUEST_CODE = "request_code"; + + private ProgressBar mProgressBar; + private TextView eipStatus; + private static Context app; + private static SharedPreferences preferences; + private static Provider provider; + + private TextView providerNameTV; + + private boolean authed_eip = false; + + public ProviderAPIResultReceiver providerAPI_result_receiver; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + app = this; + + PRNGFixes.apply(); + // mProgressBar = (ProgressBar) findViewById(R.id.progressbar_dashboard); + // mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); + // eipStatus = (TextView) findViewById(R.id.eipStatus); + + mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); + + preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + + authed_eip = preferences.getBoolean(EIP.AUTHED_EIP, false); + if (preferences.getString(Provider.KEY, "").isEmpty()) + startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); + else + buildDashboard(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data){ + if ( requestCode == CONFIGURE_LEAP || requestCode == SWITCH_PROVIDER) { + // It should be equivalent: if ( (requestCode == CONFIGURE_LEAP) || (data!= null && data.hasExtra(STOP_FIRST))) { + if ( resultCode == RESULT_OK ){ + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putInt(EIP.PARSED_SERIAL, 0).commit(); + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(EIP.AUTHED_EIP, authed_eip).commit(); + Intent updateEIP = new Intent(getApplicationContext(), EIP.class); + updateEIP.setAction(EIP.ACTION_UPDATE_EIP_SERVICE); + startService(updateEIP); + buildDashboard(); + invalidateOptionsMenu(); + if(data != null && data.hasExtra(LogInDialog.VERB)) { + View view = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0); + logInDialog(view, Bundle.EMPTY); + } + } else if(resultCode == RESULT_CANCELED && data.hasExtra(ACTION_QUIT)) { + finish(); + } else + configErrorDialog(); + } + } + + /** + * Dialog shown when encountering a configuration error. Such errors require + * reconfiguring LEAP or aborting the application. + */ + private void configErrorDialog() { + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getAppContext()); + alertBuilder.setTitle(getResources().getString(R.string.setup_error_title)); + alertBuilder + .setMessage(getResources().getString(R.string.setup_error_text)) + .setCancelable(false) + .setPositiveButton(getResources().getString(R.string.setup_error_configure_button), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivityForResult(new Intent(getAppContext(),ConfigurationWizard.class),CONFIGURE_LEAP); + } + }) + .setNegativeButton(getResources().getString(R.string.setup_error_close_button), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + SharedPreferences.Editor prefsEdit = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE).edit(); + prefsEdit.remove(Provider.KEY).commit(); + finish(); + } + }) + .show(); + } + + /** + * Inflates permanent UI elements of the View and contains logic for what + * service dependent UI elements to include. + */ + private void buildDashboard() { + provider = Provider.getInstance(); + provider.init( this ); + + setContentView(R.layout.client_dashboard); + + providerNameTV = (TextView) findViewById(R.id.providerName); + providerNameTV.setText(provider.getDomain()); + providerNameTV.setTextSize(28); + + mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); + + FragmentManager fragMan = getFragmentManager(); + if ( provider.hasEIP()){ + EipServiceFragment eipFragment = new EipServiceFragment(); + fragMan.beginTransaction().replace(R.id.servicesCollection, eipFragment, EipServiceFragment.TAG).commit(); + } + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + JSONObject provider_json; + try { + provider_json = new JSONObject(getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.KEY, "")); + JSONObject service_description = provider_json.getJSONObject(Provider.SERVICE); + + if(service_description.getBoolean(Provider.ALLOW_REGISTRATION)) { + if(authed_eip) { + menu.findItem(R.id.login_button).setVisible(false); + menu.findItem(R.id.logout_button).setVisible(true); + } else { + menu.findItem(R.id.login_button).setVisible(true); + menu.findItem(R.id.logout_button).setVisible(false); + } + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.client_dashboard, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + Intent intent; + switch (item.getItemId()){ + case R.id.about_leap: + intent = new Intent(this, AboutActivity.class); + startActivity(intent); + return true; + case R.id.switch_provider: + if (Provider.getInstance().hasEIP()){ + if (getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getBoolean(EIP.AUTHED_EIP, false)){ + logOut(); + } + eipStop(); + } + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().remove(Provider.KEY).commit(); + startActivityForResult(new Intent(this,ConfigurationWizard.class), SWITCH_PROVIDER); + return true; + case R.id.login_button: + View view = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0); + logInDialog(view, Bundle.EMPTY); + return true; + case R.id.logout_button: + logOut(); + return true; + default: + return super.onOptionsItemSelected(item); + } + + } + + @Override + public void authenticate(String username, String password) { + mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); + eipStatus = (TextView) findViewById(R.id.eipStatus); + + providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); + providerAPI_result_receiver.setReceiver(this); + + Intent provider_API_command = new Intent(this, ProviderAPI.class); + + Bundle parameters = new Bundle(); + parameters.putString(LogInDialog.USERNAME, username); + parameters.putString(LogInDialog.PASSWORD, password); + + JSONObject provider_json; + try { + provider_json = new JSONObject(preferences.getString(Provider.KEY, "")); + parameters.putString(Provider.API_URL, provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + provider_API_command.setAction(ProviderAPI.SRP_AUTH); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + + mProgressBar.setVisibility(ProgressBar.VISIBLE); + eipStatus.setText(R.string.authenticating_message); + //mProgressBar.setMax(4); + startService(provider_API_command); + } + + public void cancelAuthedEipOn() { + EipServiceFragment eipFragment = (EipServiceFragment) getFragmentManager().findFragmentByTag(EipServiceFragment.TAG); + eipFragment.checkEipSwitch(false); + } + + /** + * Asks ProviderAPI to log out. + */ + public void logOut() { + providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); + providerAPI_result_receiver.setReceiver(this); + Intent provider_API_command = new Intent(this, ProviderAPI.class); + + Bundle parameters = new Bundle(); + + JSONObject provider_json; + try { + provider_json = new JSONObject(preferences.getString(Provider.KEY, "")); + parameters.putString(Provider.API_URL, provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + provider_API_command.setAction(ProviderAPI.LOG_OUT); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + + if(mProgressBar == null) mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); + mProgressBar.setVisibility(ProgressBar.VISIBLE); + if(eipStatus == null) eipStatus = (TextView) findViewById(R.id.eipStatus); + eipStatus.setText(R.string.logout_message); + // eipStatus.setText("Starting to logout"); + + startService(provider_API_command); + //mProgressBar.setMax(1); + + } + + /** + * Shows the log in dialog. + * @param view from which the dialog is created. + */ + public void logInDialog(View view, Bundle resultData) { + FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); + Fragment previous_log_in_dialog = getFragmentManager().findFragmentByTag(LogInDialog.TAG); + if (previous_log_in_dialog != null) { + fragment_transaction.remove(previous_log_in_dialog); + } + fragment_transaction.addToBackStack(null); + + DialogFragment newFragment = LogInDialog.newInstance(); + if(resultData != null && !resultData.isEmpty()) { + newFragment.setArguments(resultData); + } + newFragment.show(fragment_transaction, LogInDialog.TAG); + } + + /** + * Asks ProviderAPI to download an authenticated OpenVPN certificate. + * @param session_id cookie for the server to allow us to download the certificate. + */ + private void downloadAuthedUserCertificate(/*Cookie session_id*/) { + providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); + providerAPI_result_receiver.setReceiver(this); + + Intent provider_API_command = new Intent(this, ProviderAPI.class); + + Bundle parameters = new Bundle(); + parameters.putString(ConfigurationWizard.TYPE_OF_CERTIFICATE, ConfigurationWizard.AUTHED_CERTIFICATE); + /*parameters.putString(ConfigHelper.SESSION_ID_COOKIE_KEY, session_id.getName()); + parameters.putString(ConfigHelper.SESSION_ID_KEY, session_id.getValue());*/ + + provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + + startService(provider_API_command); + } + + @Override + public void onReceiveResult(int resultCode, Bundle resultData) { + if(resultCode == ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL){ + String session_id_cookie_key = resultData.getString(ProviderAPI.SESSION_ID_COOKIE_KEY); + String session_id_string = resultData.getString(ProviderAPI.SESSION_ID_KEY); + setResult(RESULT_OK); + + authed_eip = true; + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(EIP.AUTHED_EIP, authed_eip).commit(); + + invalidateOptionsMenu(); + mProgressBar.setVisibility(ProgressBar.GONE); + changeStatusMessage(resultCode); + + //Cookie session_id = new BasicClientCookie(session_id_cookie_key, session_id_string); + downloadAuthedUserCertificate(/*session_id*/); + } else if(resultCode == ProviderAPI.SRP_AUTHENTICATION_FAILED) { + logInDialog(getCurrentFocus(), resultData); + } else if(resultCode == ProviderAPI.LOGOUT_SUCCESSFUL) { + authed_eip = false; + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(EIP.AUTHED_EIP, authed_eip).commit(); + mProgressBar.setVisibility(ProgressBar.GONE); + mProgressBar.setProgress(0); + invalidateOptionsMenu(); + setResult(RESULT_OK); + changeStatusMessage(resultCode); + + } else if(resultCode == ProviderAPI.LOGOUT_FAILED) { + setResult(RESULT_CANCELED); + changeStatusMessage(resultCode); + mProgressBar.setVisibility(ProgressBar.GONE); + } else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { + setResult(RESULT_OK); + changeStatusMessage(resultCode); + mProgressBar.setVisibility(ProgressBar.GONE); + if(EipServiceFragment.isEipSwitchChecked()) + eipStart(); + } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { + setResult(RESULT_CANCELED); + changeStatusMessage(resultCode); + mProgressBar.setVisibility(ProgressBar.GONE); + } + } + + private void changeStatusMessage(final int previous_result_code) { + // TODO Auto-generated method stub + ResultReceiver eip_status_receiver = new ResultReceiver(new Handler()){ + protected void onReceiveResult(int resultCode, Bundle resultData){ + super.onReceiveResult(resultCode, resultData); + String request = resultData.getString(EIP.REQUEST_TAG); + if (request.equalsIgnoreCase(EIP.ACTION_IS_EIP_RUNNING)){ + if (resultCode == Activity.RESULT_OK){ + + switch(previous_result_code){ + case ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL: eipStatus.setText(R.string.succesful_authentication_message); break; + case ProviderAPI.SRP_AUTHENTICATION_FAILED: eipStatus.setText(R.string.authentication_failed_message); break; + case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.authed_secured_status); break; + case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.incorrectly_downloaded_certificate_message); break; + case ProviderAPI.LOGOUT_SUCCESSFUL: eipStatus.setText(R.string.anonymous_secured_status); break; + case ProviderAPI.LOGOUT_FAILED: eipStatus.setText(R.string.log_out_failed_message); break; + + } + } + else if(resultCode == Activity.RESULT_CANCELED){ + + switch(previous_result_code){ + + case ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL: eipStatus.setText(R.string.succesful_authentication_message); break; + case ProviderAPI.SRP_AUTHENTICATION_FAILED: eipStatus.setText(R.string.authentication_failed_message); break; + case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.future_authed_secured_status); break; + case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.incorrectly_downloaded_certificate_message); break; + case ProviderAPI.LOGOUT_SUCCESSFUL: eipStatus.setText(R.string.future_anonymous_secured_status); break; + case ProviderAPI.LOGOUT_FAILED: eipStatus.setText(R.string.log_out_failed_message); break; + } + } + } + + } + }; + eipIsRunning(eip_status_receiver); + } + + /** + * For retrieving the base application Context in classes that don't extend + * Android's Activity class + * + * @return Application Context as defined by this for Dashboard instance + */ + public static Context getAppContext() { + return app; + } + + + @Override + public void startActivityForResult(Intent intent, int requestCode) { + intent.putExtra(Dashboard.REQUEST_CODE, requestCode); + super.startActivityForResult(intent, requestCode); + } + /** + * Send a command to EIP + * + * @param action A valid String constant from EIP class representing an Intent + * filter for the EIP class + */ + private void eipIsRunning(ResultReceiver eip_receiver){ + // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? + Intent eip_intent = new Intent(this, EIP.class); + eip_intent.setAction(EIP.ACTION_IS_EIP_RUNNING); + eip_intent.putExtra(EIP.RECEIVER_TAG, eip_receiver); + startService(eip_intent); + } + + /** + * Send a command to EIP + * + */ + private void eipStop(){ + // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? + Intent eip_intent = new Intent(this, EIP.class); + eip_intent.setAction(EIP.ACTION_STOP_EIP); + // eip_intent.putExtra(EIP.RECEIVER_TAG, eip_receiver); + startService(eip_intent); + + } + + private void eipStart(){ + Intent eip_intent = new Intent(this, EIP.class); + eip_intent.setAction(EIP.ACTION_START_EIP); + eip_intent.putExtra(EIP.RECEIVER_TAG, EipServiceFragment.getReceiver()); + startService(eip_intent); + + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java b/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java new file mode 100644 index 00000000..f78002b0 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.NewProviderDialog.NewProviderDialogInterface; +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.os.Bundle; + +/** + * Implements a dialog to show why a download failed. + * + * @author parmegv + * + */ +public class DownloadFailedDialog extends DialogFragment { + + public static String TAG = "downloaded_failed_dialog"; + private String reason_to_fail; + /** + * @return a new instance of this DialogFragment. + */ + public static DialogFragment newInstance(String reason_to_fail) { + DownloadFailedDialog dialog_fragment = new DownloadFailedDialog(); + dialog_fragment.reason_to_fail = reason_to_fail; + return dialog_fragment; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + builder.setMessage(reason_to_fail) + .setPositiveButton(R.string.retry, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dismiss(); + interface_with_ConfigurationWizard.retrySetUpProvider(); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + interface_with_ConfigurationWizard.cancelSettingUpProvider(); + dialog.dismiss(); + } + }); + + // Create the AlertDialog object and return it + return builder.create(); + } + + public interface DownloadFailedDialogInterface { + public void retrySetUpProvider(); + public void cancelSettingUpProvider(); + } + + DownloadFailedDialogInterface interface_with_ConfigurationWizard; + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + interface_with_ConfigurationWizard = (DownloadFailedDialogInterface) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement NoticeDialogListener"); + } + } + + @Override + public void onCancel(DialogInterface dialog) { + interface_with_ConfigurationWizard.cancelSettingUpProvider(); + dialog.dismiss(); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/EIP.java b/app/src/main/java/se/leap/bitmaskclient/EIP.java new file mode 100644 index 00000000..e773e3b9 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/EIP.java @@ -0,0 +1,623 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import java.util.Calendar; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.TreeMap; +import java.util.Vector; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import se.leap.bitmaskclient.R; +import se.leap.openvpn.ConfigParser; +import se.leap.openvpn.ConfigParser.ConfigParseError; +import se.leap.openvpn.LaunchVPN; +import se.leap.openvpn.OpenVpnManagementThread; +import se.leap.openvpn.OpenVpnService; +import se.leap.openvpn.OpenVpnService.LocalBinder; +import se.leap.openvpn.ProfileManager; +import se.leap.openvpn.VpnProfile; +import android.app.Activity; +import android.app.IntentService; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.drm.DrmStore.Action; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ResultReceiver; +import android.util.Log; + +/** + * EIP is the abstract base class for interacting with and managing the Encrypted + * Internet Proxy connection. Connections are started, stopped, and queried through + * this IntentService. + * Contains logic for parsing eip-service.json from the provider, configuring and selecting + * gateways, and controlling {@link .openvpn.OpenVpnService} connections. + * + * @author Sean Leonard + */ +public final class EIP extends IntentService { + + public final static String AUTHED_EIP = "authed eip"; + public final static String ACTION_START_EIP = "se.leap.bitmaskclient.START_EIP"; + public final static String ACTION_STOP_EIP = "se.leap.bitmaskclient.STOP_EIP"; + public final static String ACTION_UPDATE_EIP_SERVICE = "se.leap.bitmaskclient.UPDATE_EIP_SERVICE"; + public final static String ACTION_IS_EIP_RUNNING = "se.leap.bitmaskclient.IS_RUNNING"; + public final static String EIP_NOTIFICATION = "EIP_NOTIFICATION"; + public final static String ALLOWED_ANON = "allow_anonymous"; + public final static String CERTIFICATE = "cert"; + public final static String PRIVATE_KEY = "private_key"; + public final static String KEY = "eip"; + public final static String PARSED_SERIAL = "eip_parsed_serial"; + public final static String SERVICE_API_PATH = "config/eip-service.json"; + public final static String RECEIVER_TAG = "receiverTag"; + public final static String REQUEST_TAG = "requestTag"; + public final static String TAG = "se.leap.bitmaskclient.EIP"; + + + private static Context context; + private static ResultReceiver mReceiver; + private static OpenVpnService mVpnService; + private static boolean mBound = false; + // Used to store actions to "resume" onServiceConnection + private static String mPending = null; + + private static int parsedEipSerial; + private static JSONObject eipDefinition = null; + + private static OVPNGateway activeGateway = null; + + public EIP(){ + super("LEAPEIP"); + } + + @Override + public void onCreate() { + super.onCreate(); + + context = getApplicationContext(); + + updateEIPService(); + + this.retreiveVpnService(); + } + + @Override + public void onDestroy() { + unbindService(mVpnServiceConn); + mBound = false; + + super.onDestroy(); + } + + @Override + protected void onHandleIntent(Intent intent) { + String action = intent.getAction(); + mReceiver = intent.getParcelableExtra(RECEIVER_TAG); + + if ( action == ACTION_IS_EIP_RUNNING ) + this.isRunning(); + if ( action == ACTION_UPDATE_EIP_SERVICE ) + this.updateEIPService(); + else if ( action == ACTION_START_EIP ) + this.startEIP(); + else if ( action == ACTION_STOP_EIP ) + this.stopEIP(); + } + + /** + * Sends an Intent to bind OpenVpnService. + * Used when OpenVpnService isn't bound but might be running. + */ + private boolean retreiveVpnService() { + Intent bindIntent = new Intent(this,OpenVpnService.class); + bindIntent.setAction(OpenVpnService.RETRIEVE_SERVICE); + return bindService(bindIntent, mVpnServiceConn, BIND_AUTO_CREATE); + } + + private static ServiceConnection mVpnServiceConn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + LocalBinder binder = (LocalBinder) service; + mVpnService = binder.getService(); + mBound = true; + + if (mReceiver != null && mPending != null) { + + boolean running = mVpnService.isRunning(); + + int resultCode = Activity.RESULT_CANCELED; + + if (mPending.equals(ACTION_IS_EIP_RUNNING)){ + resultCode = (running) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; + + } + else if (mPending.equals(ACTION_START_EIP)){ + resultCode = (running) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; + } + else if (mPending.equals(ACTION_STOP_EIP)){ + resultCode = (running) ? Activity.RESULT_CANCELED + : Activity.RESULT_OK; + } + Bundle resultData = new Bundle(); + resultData.putString(REQUEST_TAG, ACTION_IS_EIP_RUNNING); + mReceiver.send(resultCode, resultData); + + mPending = null; + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mBound = false; + + if (mReceiver != null){ + Bundle resultData = new Bundle(); + resultData.putString(REQUEST_TAG, EIP_NOTIFICATION); + mReceiver.send(Activity.RESULT_CANCELED, resultData); + } + } + + + }; + + /** + * Attempts to determine if OpenVpnService has an established VPN connection + * through the bound ServiceConnection. If there is no bound service, this + * method will attempt to bind a running OpenVpnService and send + * Activity.RESULT_CANCELED to the ResultReceiver that made the + * request. + * Note: If the request to bind OpenVpnService is successful, the ResultReceiver + * will be notified in {@link onServiceConnected()} + */ + + private void isRunning() { + Bundle resultData = new Bundle(); + resultData.putString(REQUEST_TAG, ACTION_IS_EIP_RUNNING); + int resultCode = Activity.RESULT_CANCELED; + if (mBound) { + resultCode = (mVpnService.isRunning()) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; + + if (mReceiver != null){ + mReceiver.send(resultCode, resultData); + } + } else { + mPending = ACTION_IS_EIP_RUNNING; + boolean retrieved_vpn_service = retreiveVpnService(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + boolean running = false; + try { + running = mVpnService.isRunning(); + } catch (NullPointerException e){ + e.printStackTrace(); + } + + if (retrieved_vpn_service && running && mReceiver != null){ + mReceiver.send(Activity.RESULT_OK, resultData); + } + else{ + mReceiver.send(Activity.RESULT_CANCELED, resultData); + } + } + } + + /** + * Initiates an EIP connection by selecting a gateway and preparing and sending an + * Intent to {@link se.leap.openvpn.LaunchVPN} + */ + private void startEIP() { + activeGateway = selectGateway(); + + Intent intent = new Intent(this,LaunchVPN.class); + intent.setAction(Intent.ACTION_MAIN); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(LaunchVPN.EXTRA_KEY, activeGateway.mVpnProfile.getUUID().toString() ); + intent.putExtra(LaunchVPN.EXTRA_NAME, activeGateway.mVpnProfile.getName() ); + intent.putExtra(RECEIVER_TAG, mReceiver); + startActivity(intent); + mPending = ACTION_START_EIP; + } + + /** + * Disconnects the EIP connection gracefully through the bound service or forcefully + * if there is no bound service. Sends a message to the requesting ResultReceiver. + */ + private void stopEIP() { + if (mBound) + mVpnService.onRevoke(); + else + OpenVpnManagementThread.stopOpenVPN(); + + if (mReceiver != null){ + Bundle resultData = new Bundle(); + resultData.putString(REQUEST_TAG, ACTION_STOP_EIP); + mReceiver.send(Activity.RESULT_OK, resultData); + } + } + + /** + * Loads eip-service.json from SharedPreferences and calls {@link updateGateways()} + * to parse gateway definitions. + * TODO Implement API call to refresh eip-service.json from the provider + */ + private void updateEIPService() { + try { + eipDefinition = new JSONObject(getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(KEY, "")); + parsedEipSerial = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getInt(PARSED_SERIAL, 0); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if(parsedEipSerial == 0) { + // Delete all vpn profiles + ProfileManager vpl = ProfileManager.getInstance(context); + VpnProfile[] profiles = (VpnProfile[]) vpl.getProfiles().toArray(new VpnProfile[vpl.getProfiles().size()]); + for (int current_profile = 0; current_profile < profiles.length; current_profile++){ + vpl.removeProfile(context, profiles[current_profile]); + } + } + if (eipDefinition.optInt("serial") > parsedEipSerial) + updateGateways(); + } + + /** + * Choose a gateway to connect to based on timezone from system locale data + * + * @return The gateway to connect to + */ + private OVPNGateway selectGateway() { + // TODO Remove String arg constructor in favor of findGatewayByName(String) + + Calendar cal = Calendar.getInstance(); + int localOffset = cal.get(Calendar.ZONE_OFFSET) / 3600000; + TreeMap> offsets = new TreeMap>(); + JSONObject locationsObjects = null; + Iterator locations = null; + try { + locationsObjects = eipDefinition.getJSONObject("locations"); + locations = locationsObjects.keys(); + } catch (JSONException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + while (locations.hasNext()) { + String locationName = locations.next(); + JSONObject location = null; + try { + location = locationsObjects.getJSONObject(locationName); + + // Distance along the numberline of Prime Meridian centric, assumes UTC-11 through UTC+12 + int dist = Math.abs(localOffset - location.optInt("timezone")); + // Farther than 12 timezones and it's shorter around the "back" + if (dist > 12) + dist = 12 - (dist -12); // Well i'll be. Absolute values make equations do funny things. + + Set set = offsets.get(dist); + if (set == null) set = new HashSet(); + set.add(locationName); + offsets.put(dist, set); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + + String closestLocation = offsets.isEmpty() ? "" : offsets.firstEntry().getValue().iterator().next(); + JSONArray gateways = null; + String chosenHost = null; + try { + gateways = eipDefinition.getJSONArray("gateways"); + for (int i = 0; i < gateways.length(); i++) { + JSONObject gw = gateways.getJSONObject(i); + if ( gw.getString("location").equalsIgnoreCase(closestLocation) || closestLocation.isEmpty()){ + chosenHost = gw.getString("host"); + break; + } + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return new OVPNGateway(chosenHost); + } + + /** + * Walk the list of gateways defined in eip-service.json and parse them into + * OVPNGateway objects. + * TODO Store the OVPNGateways (as Serializable) in SharedPreferences + */ + private void updateGateways(){ + JSONArray gatewaysDefined = null; + + try { + gatewaysDefined = eipDefinition.getJSONArray("gateways"); + } catch (JSONException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + for ( int i=0 ; i < gatewaysDefined.length(); i++ ){ + + JSONObject gw = null; + + try { + gw = gatewaysDefined.getJSONObject(i); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + try { + if ( gw.getJSONObject("capabilities").getJSONArray("transport").toString().contains("openvpn") ){ + new OVPNGateway(gw); + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putInt(PARSED_SERIAL, eipDefinition.optInt(Provider.API_RETURN_SERIAL)).commit(); + } + + /** + * OVPNGateway provides objects defining gateways and their options and metadata. + * Each instance contains a VpnProfile for OpenVPN specific data and member + * variables describing capabilities and location + * + * @author Sean Leonard + */ + private class OVPNGateway { + + private String TAG = "OVPNGateway"; + + private String mName; + private VpnProfile mVpnProfile; + private JSONObject mGateway; + private HashMap>> options = new HashMap>>(); + + + /** + * Attempts to retrieve a VpnProfile by name and build an OVPNGateway around it. + * FIXME This needs to become a findGatewayByName() method + * + * @param name The hostname of the gateway to inflate + */ + private OVPNGateway(String name){ + mName = name; + + this.loadVpnProfile(); + } + + private void loadVpnProfile() { + ProfileManager vpl = ProfileManager.getInstance(context); + + try { + if ( mName == null ) + mVpnProfile = vpl.getProfiles().iterator().next(); + else + mVpnProfile = vpl.getProfileByName(mName); + } catch (NoSuchElementException e) { + updateEIPService(); + this.loadVpnProfile(); // FIXME catch infinite loops + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json + * and create a VpnProfile belonging to it. + * + * @param gateway The JSON OpenVPN gateway definition to parse + */ + protected OVPNGateway(JSONObject gateway){ + + mGateway = gateway; + + // Currently deletes VpnProfile for host, if there already is one, and builds new + ProfileManager vpl = ProfileManager.getInstance(context); + Collection profiles = vpl.getProfiles(); + for (Iterator it = profiles.iterator(); it.hasNext(); ){ + VpnProfile p = it.next(); + try { + if ( p.mName.equalsIgnoreCase( gateway.getString("host") ) ){ + it.remove(); + vpl.removeProfile(context, p); + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + this.parseOptions(); + this.createVPNProfile(); + + setUniqueProfileName(vpl); + vpl.addProfile(mVpnProfile); + vpl.saveProfile(context, mVpnProfile); + vpl.saveProfileList(context); + } + + /** + * Attempts to create a unique profile name from the hostname of the gateway + * + * @param profileManager + */ + private void setUniqueProfileName(ProfileManager profileManager) { + int i=0; + + String newname; + try { + newname = mGateway.getString("host"); + while(profileManager.getProfileByName(newname)!=null) { + i++; + if(i==1) + newname = getString(R.string.converted_profile); + else + newname = getString(R.string.converted_profile_i,i); + } + + mVpnProfile.mName=newname; + } catch (JSONException e) { + // TODO Auto-generated catch block + Log.v(TAG,"Couldn't read gateway name for profile creation!"); + e.printStackTrace(); + } + } + + /** + * FIXME This method is really the outline of the refactoring needed in se.leap.openvpn.ConfigParser + */ + private void parseOptions(){ + + // FIXME move these to a common API (& version) definition place, like ProviderAPI or ConfigHelper + String common_options = "openvpn_configuration"; + String remote = "ip_address"; + String ports = "ports"; + String protos = "protocols"; + String capabilities = "capabilities"; + String location_key = "location"; + String locations = "locations"; + + Vector arg = new Vector(); + Vector> args = new Vector>(); + + try { + JSONObject def = (JSONObject) eipDefinition.get(common_options); + Iterator keys = def.keys(); + Vector> value = new Vector>(); + while ( keys.hasNext() ){ + String key = keys.next().toString(); + + arg.add(key); + for ( String word : def.getString(key).split(" ") ) + arg.add(word); + value.add( (Vector) arg.clone() ); + options.put(key, (Vector>) value.clone()); + value.clear(); + arg.clear(); + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + try { + arg.add(remote); + arg.add(mGateway.getString(remote)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + args.add((Vector) arg.clone()); + options.put("remote", (Vector>) args.clone() ); + arg.clear(); + args.clear(); + + + + try { + + arg.add(location_key); + String locationText = ""; + locationText = eipDefinition.getJSONObject(locations).getJSONObject(mGateway.getString(location_key)).getString("name"); + arg.add(locationText); + + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + args.add((Vector) arg.clone()); + options.put("location", (Vector>) args.clone() ); + + arg.clear(); + args.clear(); + JSONArray protocolsJSON = null; + arg.add("proto"); + try { + protocolsJSON = mGateway.getJSONObject(capabilities).getJSONArray(protos); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + Vector protocols = new Vector(); + for ( int i=0; i) arg.clone()); + options.put("proto", (Vector>) args.clone()); + arg.clear(); + args.clear(); + + + String port = null; + arg.add("port"); + try { + port = mGateway.getJSONObject(capabilities).getJSONArray(ports).optString(0); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + arg.add(port); + args.add((Vector) arg.clone()); + options.put("port", (Vector>) args.clone()); + args.clear(); + arg.clear(); + } + + /** + * Create and attach the VpnProfile to our gateway object + */ + protected void createVPNProfile(){ + try { + ConfigParser cp = new ConfigParser(); + cp.setDefinition(options); + VpnProfile vp = cp.convertProfile(); + mVpnProfile = vp; + Log.v(TAG,"Created VPNProfile"); + } catch (ConfigParseError e) { + // FIXME We didn't get a VpnProfile! Error handling! and log level + Log.v(TAG,"Error createing VPNProfile"); + e.printStackTrace(); + } + } + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java new file mode 100644 index 00000000..b4cb541a --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java @@ -0,0 +1,306 @@ +package se.leap.bitmaskclient; + +import se.leap.bitmaskclient.R; +import se.leap.openvpn.LogWindow; +import se.leap.openvpn.OpenVPN; +import se.leap.openvpn.OpenVPN.StateListener; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.CompoundButton; +import android.widget.RelativeLayout; +import android.widget.Switch; +import android.widget.TextView; + +public class EipServiceFragment extends Fragment implements StateListener, OnCheckedChangeListener { + + protected static final String IS_EIP_PENDING = "is_eip_pending"; + + private View eipFragment; + private static Switch eipSwitch; + private View eipDetail; + private TextView eipStatus; + + private boolean eipAutoSwitched = true; + + private boolean mEipStartPending = false; + + private boolean set_switch_off = false; + + private static EIPReceiver mEIPReceiver; + + + public static String TAG = "se.leap.bitmask.EipServiceFragment"; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + eipFragment = inflater.inflate(R.layout.eip_service_fragment, container, false); + + eipDetail = ((RelativeLayout) eipFragment.findViewById(R.id.eipDetail)); + eipDetail.setVisibility(View.VISIBLE); + + View eipSettings = eipFragment.findViewById(R.id.eipSettings); + eipSettings.setVisibility(View.GONE); // FIXME too! + + if (mEipStartPending) + eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); + + eipStatus = (TextView) eipFragment.findViewById(R.id.eipStatus); + + eipSwitch = (Switch) eipFragment.findViewById(R.id.eipSwitch); + + + eipSwitch.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + eipAutoSwitched = false; + return false; + } + }); + eipSwitch.setOnCheckedChangeListener(this); + + + return eipFragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mEIPReceiver = new EIPReceiver(new Handler()); + + if (savedInstanceState != null) + mEipStartPending = savedInstanceState.getBoolean(IS_EIP_PENDING); + } + + @Override + public void onResume() { + super.onResume(); + + OpenVPN.addStateListener(this); + if(set_switch_off) { + eipSwitch.setChecked(false); + set_switch_off = false; + } + } + + protected void setSwitchOff(boolean value) { + set_switch_off = value; + } + + @Override + public void onPause() { + super.onPause(); + + OpenVPN.removeStateListener(this); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(IS_EIP_PENDING, mEipStartPending); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView.equals(eipSwitch) && !eipAutoSwitched){ + boolean allowed_anon = getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).getBoolean(EIP.ALLOWED_ANON, false); + String certificate = getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).getString(EIP.CERTIFICATE, ""); + if(allowed_anon || !certificate.isEmpty()) { + if (isChecked){ + mEipStartPending = true; + eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); + ((TextView) eipFragment.findViewById(R.id.eipStatus)).setText(R.string.eip_status_start_pending); + eipCommand(EIP.ACTION_START_EIP); + } else { + if (mEipStartPending){ + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity()); + alertBuilder.setTitle(getResources().getString(R.string.eip_cancel_connect_title)); + alertBuilder + .setMessage(getResources().getString(R.string.eip_cancel_connect_text)) + .setPositiveButton(getResources().getString(R.string.eip_cancel_connect_cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + eipCommand(EIP.ACTION_STOP_EIP); + mEipStartPending = false; + } + }) + .setNegativeButton(getResources().getString(R.string.eip_cancel_connect_false), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + eipAutoSwitched = true; + eipSwitch.setChecked(true); + eipAutoSwitched = false; + } + }) + .show(); + } else { + eipCommand(EIP.ACTION_STOP_EIP); + } + } + } + else { + Dashboard dashboard = (Dashboard)getActivity(); + Bundle waiting_on_login = new Bundle(); + waiting_on_login.putBoolean(IS_EIP_PENDING, true); + dashboard.logInDialog(getActivity().getCurrentFocus(), waiting_on_login); + } + } + else { + if(!eipSwitch.isChecked()) + eipStatus.setText(R.string.state_noprocess); + } + eipAutoSwitched = true; + } + + + + /** + * Send a command to EIP + * + * @param action A valid String constant from EIP class representing an Intent + * filter for the EIP class + */ + private void eipCommand(String action){ + // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? + Intent vpnIntent = new Intent(action); + vpnIntent.putExtra(EIP.RECEIVER_TAG, mEIPReceiver); + getActivity().startService(vpnIntent); + } + + @Override + public void updateState(final String state, final String logmessage, final int localizedResId) { + // Note: "states" are not organized anywhere...collected state strings: + // NOPROCESS,NONETWORK,BYTECOUNT,AUTH_FAILED + some parsing thing ( WAIT(?),AUTH,GET_CONFIG,ASSIGN_IP,CONNECTED,SIGINT ) + getActivity().runOnUiThread(new Runnable() { + + @Override + public void run() { + if (eipStatus != null) { + boolean switchState = true; + String statusMessage = ""; + String prefix = getString(localizedResId); + if (state.equals("CONNECTED")){ + + statusMessage = getString(R.string.eip_state_connected); + getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE); + mEipStartPending = false; + } else if (state.equals("BYTECOUNT")) { + statusMessage = getString(R.string.eip_state_connected); getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE); + mEipStartPending = false; + + } else if ( (state.equals("NOPROCESS") && !mEipStartPending ) || state.equals("EXITING") && !mEipStartPending || state.equals("FATAL")) { + statusMessage = getString(R.string.eip_state_not_connected); + getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE); + mEipStartPending = false; + switchState = false; + } else if (state.equals("NOPROCESS")){ + statusMessage = logmessage; + } else if (state.equals("ASSIGN_IP")){ //don't show assigning message in eipStatus + statusMessage = (String) eipStatus.getText(); + } + else { + statusMessage = prefix + " " + logmessage; + } + + eipAutoSwitched = true; + eipSwitch.setChecked(switchState); + eipAutoSwitched = false; + eipStatus.setText(statusMessage); + } + } + }); + } + + + /** + * Inner class for handling messages related to EIP status and control requests + * + * @author Sean Leonard + */ + protected class EIPReceiver extends ResultReceiver { + + protected EIPReceiver(Handler handler){ + super(handler); + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + super.onReceiveResult(resultCode, resultData); + + String request = resultData.getString(EIP.REQUEST_TAG); + boolean checked = false; + + if (request == EIP.ACTION_IS_EIP_RUNNING) { + switch (resultCode){ + case Activity.RESULT_OK: + checked = true; + break; + case Activity.RESULT_CANCELED: + checked = false; + break; + } + } else if (request == EIP.ACTION_START_EIP) { + switch (resultCode){ + case Activity.RESULT_OK: + checked = true; + break; + case Activity.RESULT_CANCELED: + checked = false; + eipFragment.findViewById(R.id.eipProgress).setVisibility(View.GONE); + break; + } + } else if (request == EIP.ACTION_STOP_EIP) { + switch (resultCode){ + case Activity.RESULT_OK: + checked = false; + break; + case Activity.RESULT_CANCELED: + checked = true; + break; + } + } else if (request == EIP.EIP_NOTIFICATION) { + switch (resultCode){ + case Activity.RESULT_OK: + checked = true; + break; + case Activity.RESULT_CANCELED: + checked = false; + break; + } + } + + eipAutoSwitched = true; + eipSwitch.setChecked(checked); + eipAutoSwitched = false; + } + } + + + public static EIPReceiver getReceiver() { + return mEIPReceiver; + } + + public static boolean isEipSwitchChecked() { + return eipSwitch.isChecked(); + } + + public void checkEipSwitch(boolean checked) { + eipSwitch.setChecked(checked); + onCheckedChanged(eipSwitch, checked); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/LeapHttpClient.java b/app/src/main/java/se/leap/bitmaskclient/LeapHttpClient.java new file mode 100644 index 00000000..885b5105 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/LeapHttpClient.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import java.security.KeyStore; + +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.SingleClientConnManager; +import android.content.Context; + +/** + * Implements an HTTP client, enabling LEAP Android app to manage its own runtime keystore or bypass default Android security measures. + * + * @author rafa + * + */ +public class LeapHttpClient extends DefaultHttpClient { + + private static LeapHttpClient client; + + /** + * If the class scope client is null, it creates one and imports, if existing, the main certificate from Shared Preferences. + * @param context + * @return the new client. + */ + public static LeapHttpClient getInstance(String cert_string) { + if(client == null) { + if(cert_string != null) { + ConfigHelper.addTrustedCertificate("provider_ca_certificate", cert_string); + } + } + return client; + } + + @Override + protected ClientConnectionManager createClientConnectionManager() { + SchemeRegistry registry = new SchemeRegistry(); + registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); + registry.register(new Scheme("https", newSslSocketFactory(), 443)); + + return new SingleClientConnManager(getParams(), registry); + } + + /** + * Uses keystore from ConfigHelper for the SSLSocketFactory. + * @return + */ + private SSLSocketFactory newSslSocketFactory() { + try { + KeyStore trusted = ConfigHelper.getKeystore(); + SSLSocketFactory sf = new SSLSocketFactory(trusted); + + return sf; + } catch (Exception e) { + throw new AssertionError(e); + } + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java new file mode 100644 index 00000000..a317d95e --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java @@ -0,0 +1,341 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; + +import org.jboss.security.srp.SRPParameters; + +/** + * Implements all SRP algorithm logic. + * + * It's derived from JBoss implementation, with adjustments to make it work with LEAP platform. + * + * @author parmegv + * + */ +public class LeapSRPSession { + + private static String token = ""; + + final public static String SALT = "salt"; + final public static String M1 = "M1"; + final public static String M2 = "M2"; + final public static String TOKEN = "token"; + final public static String AUTHORIZATION_HEADER= "Authorization"; + + private SRPParameters params; + private String username; + private String password; + private BigInteger N; + private byte[] N_bytes; + private BigInteger g; + private BigInteger x; + private BigInteger v; + private BigInteger a; + private BigInteger A; + private byte[] K; + private SecureRandom pseudoRng; + /** The M1 = H(H(N) xor H(g) | H(U) | s | A | B | K) hash */ + private MessageDigest clientHash; + /** The M2 = H(A | M | K) hash */ + private MessageDigest serverHash; + + private static int A_LEN; + + /** Creates a new SRP server session object from the username, password + verifier, + @param username, the user ID + @param password, the user clear text password + @param params, the SRP parameters for the session + */ + public LeapSRPSession(String username, String password, SRPParameters params) + { + this(username, password, params, null); + } + + /** Creates a new SRP server session object from the username, password + verifier, + @param username, the user ID + @param password, the user clear text password + @param params, the SRP parameters for the session + @param abytes, the random exponent used in the A public key + */ + public LeapSRPSession(String username, String password, SRPParameters params, + byte[] abytes) { + this.params = params; + this.g = new BigInteger(1, params.g); + N_bytes = ConfigHelper.trim(params.N); + this.N = new BigInteger(1, N_bytes); + this.username = username; + this.password = password; + + try { + pseudoRng = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + if( abytes != null ) { + A_LEN = 8*abytes.length; + /* TODO Why did they put this condition? + if( 8*abytes.length != A_LEN ) + throw new IllegalArgumentException("The abytes param must be " + +(A_LEN/8)+" in length, abytes.length="+abytes.length); + */ + this.a = new BigInteger(abytes); + } + else + A_LEN = 64; + + serverHash = newDigest(); + clientHash = newDigest(); + } + + /** + * Calculates the parameter x of the SRP-6a algorithm. + * @param username + * @param password + * @param salt the salt of the user + * @return x + */ + public byte[] calculatePasswordHash(String username, String password, byte[] salt) + { + //password = password.replaceAll("\\\\", "\\\\\\\\"); + // Calculate x = H(s | H(U | ':' | password)) + MessageDigest x_digest = newDigest(); + // Try to convert the username to a byte[] using ISO-8859-1 + byte[] user = null; + byte[] password_bytes = null; + byte[] colon = {}; + String encoding = "ISO-8859-1"; + try { + user = ConfigHelper.trim(username.getBytes(encoding)); + colon = ConfigHelper.trim(":".getBytes(encoding)); + password_bytes = ConfigHelper.trim(password.getBytes(encoding)); + } + catch(UnsupportedEncodingException e) { + // Use the default platform encoding + user = ConfigHelper.trim(username.getBytes()); + colon = ConfigHelper.trim(":".getBytes()); + password_bytes = ConfigHelper.trim(password.getBytes()); + } + + // Build the hash + x_digest.update(user); + x_digest.update(colon); + x_digest.update(password_bytes); + byte[] h = x_digest.digest(); + + x_digest.reset(); + x_digest.update(salt); + x_digest.update(h); + byte[] x_digest_bytes = x_digest.digest(); + + return x_digest_bytes; + } + + /** + * Calculates the parameter V of the SRP-6a algorithm. + * @param k_string constant k predefined by the SRP server implementation. + * @return the value of V + */ + private BigInteger calculateV(String k_string) { + BigInteger k = new BigInteger(k_string, 16); + BigInteger v = k.multiply(g.modPow(x, N)); // g^x % N + return v; + } + + /** + * Calculates the trimmed xor from two BigInteger numbers + * @param b1 the positive source to build first BigInteger + * @param b2 the positive source to build second BigInteger + * @param length + * @return + */ + public byte[] xor(byte[] b1, byte[] b2) + { + //TODO Check if length matters in the order, when b2 is smaller than b1 or viceversa + byte[] xor_digest = new BigInteger(1, b1).xor(new BigInteger(1, b2)).toByteArray(); + return ConfigHelper.trim(xor_digest); + } + + /** + * @returns The exponential residue (parameter A) to be sent to the server. + */ + public byte[] exponential() { + byte[] Abytes = null; + if(A == null) { + /* If the random component of A has not been specified use a random + number */ + if( a == null ) { + BigInteger one = BigInteger.ONE; + do { + a = new BigInteger(A_LEN, pseudoRng); + } while(a.compareTo(one) <= 0); + } + A = g.modPow(a, N); + Abytes = ConfigHelper.trim(A.toByteArray()); + } + return Abytes; + } + + /** + * Calculates the parameter M1, to be sent to the SRP server. + * It also updates hashes of client and server for further calculations in other methods. + * It uses a predefined k. + * @param salt_bytes + * @param Bbytes the parameter received from the server, in bytes + * @return the parameter M1 + * @throws NoSuchAlgorithmException + */ + public byte[] response(byte[] salt_bytes, byte[] Bbytes) throws NoSuchAlgorithmException { + // Calculate x = H(s | H(U | ':' | password)) + byte[] M1 = null; + if(new BigInteger(1, Bbytes).mod(new BigInteger(1, N_bytes)) != BigInteger.ZERO) { + byte[] xb = calculatePasswordHash(username, password, ConfigHelper.trim(salt_bytes)); + this.x = new BigInteger(1, xb); + + // Calculate v = kg^x mod N + String k_string = "bf66c44a428916cad64aa7c679f3fd897ad4c375e9bbb4cbf2f5de241d618ef0"; + this.v = calculateV(k_string); + + // H(N) + byte[] digest_of_n = newDigest().digest(N_bytes); + + // H(g) + byte[] digest_of_g = newDigest().digest(params.g); + + // clientHash = H(N) xor H(g) + byte[] xor_digest = xor(digest_of_n, digest_of_g); + clientHash.update(xor_digest); + + // clientHash = H(N) xor H(g) | H(U) + byte[] username_digest = newDigest().digest(ConfigHelper.trim(username.getBytes())); + username_digest = ConfigHelper.trim(username_digest); + clientHash.update(username_digest); + + // clientHash = H(N) xor H(g) | H(U) | s + clientHash.update(ConfigHelper.trim(salt_bytes)); + + K = null; + + // clientHash = H(N) xor H(g) | H(U) | A + byte[] Abytes = ConfigHelper.trim(A.toByteArray()); + clientHash.update(Abytes); + + // clientHash = H(N) xor H(g) | H(U) | s | A | B + Bbytes = ConfigHelper.trim(Bbytes); + clientHash.update(Bbytes); + + // Calculate S = (B - kg^x) ^ (a + u * x) % N + BigInteger S = calculateS(Bbytes); + byte[] S_bytes = ConfigHelper.trim(S.toByteArray()); + + // K = SessionHash(S) + String hash_algorithm = params.hashAlgorithm; + MessageDigest sessionDigest = MessageDigest.getInstance(hash_algorithm); + K = ConfigHelper.trim(sessionDigest.digest(S_bytes)); + + // clientHash = H(N) xor H(g) | H(U) | A | B | K + clientHash.update(K); + + M1 = ConfigHelper.trim(clientHash.digest()); + + // serverHash = Astr + M + K + serverHash.update(Abytes); + serverHash.update(M1); + serverHash.update(K); + + } + return M1; + } + + /** + * It calculates the parameter S used by response() to obtain session hash K. + * @param Bbytes the parameter received from the server, in bytes + * @return the parameter S + */ + private BigInteger calculateS(byte[] Bbytes) { + byte[] Abytes = ConfigHelper.trim(A.toByteArray()); + Bbytes = ConfigHelper.trim(Bbytes); + byte[] u_bytes = getU(Abytes, Bbytes); + + BigInteger B = new BigInteger(1, Bbytes); + BigInteger u = new BigInteger(1, u_bytes); + + BigInteger B_minus_v = B.subtract(v); + BigInteger a_ux = a.add(u.multiply(x)); + BigInteger S = B_minus_v.modPow(a_ux, N); + return S; + } + + /** + * It calculates the parameter u used by calculateS to obtain S. + * @param Abytes the exponential residue sent to the server + * @param Bbytes the parameter received from the server, in bytes + * @return + */ + public byte[] getU(byte[] Abytes, byte[] Bbytes) { + MessageDigest u_digest = newDigest(); + u_digest.update(ConfigHelper.trim(Abytes)); + u_digest.update(ConfigHelper.trim(Bbytes)); + byte[] u_digest_bytes = u_digest.digest(); + return ConfigHelper.trim(new BigInteger(1, u_digest_bytes).toByteArray()); + } + + /** + * @param M2 The server's response to the client's challenge + * @returns True if and only if the server's response was correct. + */ + public boolean verify(byte[] M2) + { + // M2 = H(A | M1 | K) + M2 = ConfigHelper.trim(M2); + byte[] myM2 = ConfigHelper.trim(serverHash.digest()); + boolean valid = Arrays.equals(M2, myM2); + return valid; + } + + protected static void setToken(String token) { + LeapSRPSession.token = token; + } + + protected static String getToken() { + return token; + } + + /** + * @return a new SHA-256 digest. + */ + public MessageDigest newDigest() + { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return md; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/LogInDialog.java b/app/src/main/java/se/leap/bitmaskclient/LogInDialog.java new file mode 100644 index 00000000..a28c9049 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/LogInDialog.java @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import se.leap.bitmaskclient.R; +import android.R.color; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.content.res.ColorStateList; +import android.os.Bundle; +import android.provider.CalendarContract.Colors; +import android.view.LayoutInflater; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.view.animation.BounceInterpolator; +import android.widget.EditText; +import android.widget.TextView; + +/** + * Implements the log in dialog, currently without progress dialog. + * + * It returns to the previous fragment when finished, and sends username and password to the authenticate method. + * + * It also notifies the user if the password is not valid. + * + * @author parmegv + * + */ +public class LogInDialog extends DialogFragment { + + + final public static String TAG = "logInDialog"; + final public static String VERB = "log in"; + final public static String USERNAME = "username"; + final public static String PASSWORD = "password"; + final public static String USERNAME_MISSING = "username missing"; + final public static String PASSWORD_INVALID_LENGTH = "password_invalid_length"; + + private static boolean is_eip_pending = false; + + public AlertDialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + LayoutInflater inflater = getActivity().getLayoutInflater(); + View log_in_dialog_view = inflater.inflate(R.layout.log_in_dialog, null); + + final TextView user_message = (TextView)log_in_dialog_view.findViewById(R.id.user_message); + if(getArguments() != null && getArguments().containsKey(getResources().getString(R.string.user_message))) { + user_message.setText(getArguments().getString(getResources().getString(R.string.user_message))); + } else { + user_message.setVisibility(View.GONE); + } + + final EditText username_field = (EditText)log_in_dialog_view.findViewById(R.id.username_entered); + if(getArguments() != null && getArguments().containsKey(USERNAME)) { + String username = getArguments().getString(USERNAME); + username_field.setText(username); + } + if (getArguments() != null && getArguments().containsKey(USERNAME_MISSING)) { + username_field.setError(getResources().getString(R.string.username_ask)); + } + + final EditText password_field = (EditText)log_in_dialog_view.findViewById(R.id.password_entered); + if(!username_field.getText().toString().isEmpty() && password_field.isFocusable()) { + password_field.requestFocus(); + } + if (getArguments() != null && getArguments().containsKey(PASSWORD_INVALID_LENGTH)) { + password_field.setError(getResources().getString(R.string.error_not_valid_password_user_message)); + } + if(getArguments() != null && getArguments().getBoolean(EipServiceFragment.IS_EIP_PENDING, false)) { + is_eip_pending = true; + } + + + builder.setView(log_in_dialog_view) + .setPositiveButton(R.string.login_button, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + String username = username_field.getText().toString(); + String password = password_field.getText().toString(); + dialog.dismiss(); + interface_with_Dashboard.authenticate(username, password); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + return builder.create(); + } + + /** + * Interface used to communicate LogInDialog with Dashboard. + * + * @author parmegv + * + */ + public interface LogInDialogInterface { + /** + * Starts authentication process. + * @param username + * @param password + */ + public void authenticate(String username, String password); + public void cancelAuthedEipOn(); + } + + LogInDialogInterface interface_with_Dashboard; + + /** + * @return a new instance of this DialogFragment. + */ + public static DialogFragment newInstance() { + LogInDialog dialog_fragment = new LogInDialog(); + return dialog_fragment; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + interface_with_Dashboard = (LogInDialogInterface) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement LogInDialogListener"); + } + } + + @Override + public void onCancel(DialogInterface dialog) { + if(is_eip_pending) + interface_with_Dashboard.cancelAuthedEipOn(); + super.onCancel(dialog); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/PRNGFixes.java b/app/src/main/java/se/leap/bitmaskclient/PRNGFixes.java new file mode 100644 index 00000000..a046f01f --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/PRNGFixes.java @@ -0,0 +1,338 @@ +package se.leap.bitmaskclient; + +/* + * This software is provided 'as-is', without any express or implied + * warranty. In no event will Google be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, as long as the origin is not misrepresented. + * + * Source: http://android-developers.blogspot.de/2013/08/some-securerandom-thoughts.html + */ + +import android.os.Build; +import android.os.Process; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.SecureRandomSpi; +import java.security.Security; + +/** + * Fixes for the output of the default PRNG having low entropy. + * + * The fixes need to be applied via {@link #apply()} before any use of Java + * Cryptography Architecture primitives. A good place to invoke them is in the + * application's {@code onCreate}. + */ +public final class PRNGFixes { + + private static final int VERSION_CODE_JELLY_BEAN = 16; + private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; + private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = + getBuildFingerprintAndDeviceSerial(); + + /** Hidden constructor to prevent instantiation. */ + private PRNGFixes() {} + + /** + * Applies all fixes. + * + * @throws SecurityException if a fix is needed but could not be applied. + */ + public static void apply() { + applyOpenSSLFix(); + installLinuxPRNGSecureRandom(); + } + + /** + * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the + * fix is not needed. + * + * @throws SecurityException if the fix is needed but could not be applied. + */ + private static void applyOpenSSLFix() throws SecurityException { + if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) + || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { + // No need to apply the fix + return; + } + + try { + // Mix in the device- and invocation-specific seed. + Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_seed", byte[].class) + .invoke(null, generateSeed()); + + // Mix output of Linux PRNG into OpenSSL's PRNG + int bytesRead = (Integer) Class.forName( + "org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_load_file", String.class, long.class) + .invoke(null, "/dev/urandom", 1024); + if (bytesRead != 1024) { + throw new IOException( + "Unexpected number of bytes read from Linux PRNG: " + + bytesRead); + } + } catch (Exception e) { + throw new SecurityException("Failed to seed OpenSSL PRNG", e); + } + } + + /** + * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the + * default. Does nothing if the implementation is already the default or if + * there is not need to install the implementation. + * + * @throws SecurityException if the fix is needed but could not be applied. + */ + private static void installLinuxPRNGSecureRandom() + throws SecurityException { + if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { + // No need to apply the fix + return; + } + + // Install a Linux PRNG-based SecureRandom implementation as the + // default, if not yet installed. + Provider[] secureRandomProviders = + Security.getProviders("SecureRandom.SHA1PRNG"); + if ((secureRandomProviders == null) + || (secureRandomProviders.length < 1) + || (!LinuxPRNGSecureRandomProvider.class.equals( + secureRandomProviders[0].getClass()))) { + Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); + } + + // Assert that new SecureRandom() and + // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed + // by the Linux PRNG-based SecureRandom implementation. + SecureRandom rng1 = new SecureRandom(); + if (!LinuxPRNGSecureRandomProvider.class.equals( + rng1.getProvider().getClass())) { + throw new SecurityException( + "new SecureRandom() backed by wrong Provider: " + + rng1.getProvider().getClass()); + } + + SecureRandom rng2; + try { + rng2 = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new SecurityException("SHA1PRNG not available", e); + } + if (!LinuxPRNGSecureRandomProvider.class.equals( + rng2.getProvider().getClass())) { + throw new SecurityException( + "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + + " Provider: " + rng2.getProvider().getClass()); + } + } + + /** + * {@code Provider} of {@code SecureRandom} engines which pass through + * all requests to the Linux PRNG. + */ + private static class LinuxPRNGSecureRandomProvider extends Provider { + + public LinuxPRNGSecureRandomProvider() { + super("LinuxPRNG", + 1.0, + "A Linux-specific random number provider that uses" + + " /dev/urandom"); + // Although /dev/urandom is not a SHA-1 PRNG, some apps + // explicitly request a SHA1PRNG SecureRandom and we thus need to + // prevent them from getting the default implementation whose output + // may have low entropy. + put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); + put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); + } + } + + /** + * {@link SecureRandomSpi} which passes all requests to the Linux PRNG + * ({@code /dev/urandom}). + */ + public static class LinuxPRNGSecureRandom extends SecureRandomSpi { + + /* + * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed + * are passed through to the Linux PRNG (/dev/urandom). Instances of + * this class seed themselves by mixing in the current time, PID, UID, + * build fingerprint, and hardware serial number (where available) into + * Linux PRNG. + * + * Concurrency: Read requests to the underlying Linux PRNG are + * serialized (on sLock) to ensure that multiple threads do not get + * duplicated PRNG output. + */ + + private static final File URANDOM_FILE = new File("/dev/urandom"); + + private static final Object sLock = new Object(); + + /** + * Input stream for reading from Linux PRNG or {@code null} if not yet + * opened. + * + * @GuardedBy("sLock") + */ + private static DataInputStream sUrandomIn; + + /** + * Output stream for writing to Linux PRNG or {@code null} if not yet + * opened. + * + * @GuardedBy("sLock") + */ + private static OutputStream sUrandomOut; + + /** + * Whether this engine instance has been seeded. This is needed because + * each instance needs to seed itself if the client does not explicitly + * seed it. + */ + private boolean mSeeded; + + @Override + protected void engineSetSeed(byte[] bytes) { + try { + OutputStream out; + synchronized (sLock) { + out = getUrandomOutputStream(); + } + out.write(bytes); + out.flush(); + } catch (IOException e) { + // On a small fraction of devices /dev/urandom is not writable. + // Log and ignore. + Log.w(PRNGFixes.class.getSimpleName(), + "Failed to mix seed into " + URANDOM_FILE); + } finally { + mSeeded = true; + } + } + + @Override + protected void engineNextBytes(byte[] bytes) { + if (!mSeeded) { + // Mix in the device- and invocation-specific seed. + engineSetSeed(generateSeed()); + } + + try { + DataInputStream in; + synchronized (sLock) { + in = getUrandomInputStream(); + } + synchronized (in) { + in.readFully(bytes); + } + } catch (IOException e) { + throw new SecurityException( + "Failed to read from " + URANDOM_FILE, e); + } + } + + @Override + protected byte[] engineGenerateSeed(int size) { + byte[] seed = new byte[size]; + engineNextBytes(seed); + return seed; + } + + private DataInputStream getUrandomInputStream() { + synchronized (sLock) { + if (sUrandomIn == null) { + // NOTE: Consider inserting a BufferedInputStream between + // DataInputStream and FileInputStream if you need higher + // PRNG output performance and can live with future PRNG + // output being pulled into this process prematurely. + try { + sUrandomIn = new DataInputStream( + new FileInputStream(URANDOM_FILE)); + } catch (IOException e) { + throw new SecurityException("Failed to open " + + URANDOM_FILE + " for reading", e); + } + } + return sUrandomIn; + } + } + + private OutputStream getUrandomOutputStream() throws IOException { + synchronized (sLock) { + if (sUrandomOut == null) { + sUrandomOut = new FileOutputStream(URANDOM_FILE); + } + return sUrandomOut; + } + } + } + + /** + * Generates a device- and invocation-specific seed to be mixed into the + * Linux PRNG. + */ + private static byte[] generateSeed() { + try { + ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); + DataOutputStream seedBufferOut = + new DataOutputStream(seedBuffer); + seedBufferOut.writeLong(System.currentTimeMillis()); + seedBufferOut.writeLong(System.nanoTime()); + seedBufferOut.writeInt(Process.myPid()); + seedBufferOut.writeInt(Process.myUid()); + seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); + seedBufferOut.close(); + return seedBuffer.toByteArray(); + } catch (IOException e) { + throw new SecurityException("Failed to generate seed", e); + } + } + + /** + * Gets the hardware serial number of this device. + * + * @return serial number or {@code null} if not available. + */ + private static String getDeviceSerialNumber() { + // We're using the Reflection API because Build.SERIAL is only available + // since API Level 9 (Gingerbread, Android 2.3). + try { + return (String) Build.class.getField("SERIAL").get(null); + } catch (Exception ignored) { + return null; + } + } + + private static byte[] getBuildFingerprintAndDeviceSerial() { + StringBuilder result = new StringBuilder(); + String fingerprint = Build.FINGERPRINT; + if (fingerprint != null) { + result.append(fingerprint); + } + String serial = getDeviceSerialNumber(); + if (serial != null) { + result.append(serial); + } + try { + return result.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 encoding not supported"); + } + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java new file mode 100644 index 00000000..216f4261 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -0,0 +1,212 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Locale; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.content.Context; +import android.app.Activity; +import android.content.SharedPreferences; + +/** + * @author Sean Leonard + * + */ +public final class Provider implements Serializable { + + private static final long serialVersionUID = 6003835972151761353L; + + private static Provider instance = null; + + // We'll access our preferences here + private static SharedPreferences preferences = null; + // Represents our Provider's provider.json + private static JSONObject definition = null; + + final public static String + API_URL = "api_uri", + API_VERSION = "api_version", + ALLOW_REGISTRATION = "allow_registration", + API_RETURN_SERIAL = "serial", + SERVICE = "service", + KEY = "provider", + CA_CERT = "ca_cert", + NAME = "name", + DESCRIPTION = "description", + DOMAIN = "domain", + MAIN_URL = "main_url", + DOT_JSON_URL = "provider_json_url" + ; + + // Array of what API versions we understand + protected static final String[] API_VERSIONS = {"1"}; // I assume we might encounter arbitrary version "numbers" + // Some API pieces we want to know about + private static final String API_TERM_SERVICES = "services"; + private static final String API_TERM_NAME = "name"; + private static final String API_TERM_DOMAIN = "domain"; + private static final String API_TERM_DEFAULT_LANGUAGE = "default_language"; + protected static final String[] API_EIP_TYPES = {"openvpn"}; + + private static final String PREFS_EIP_NAME = null; + + + + // What, no individual fields?! We're going to gamble on org.json.JSONObject and JSONArray + // Supporting multiple API versions will probably break this paradigm, + // Forcing me to write a real constructor and rewrite getters/setters + // Also will refactor if i'm instantiating the same local variables all the time + + /** + * + */ + private Provider() {} + + protected static Provider getInstance(){ + if(instance==null){ + instance = new Provider(); + } + return instance; + } + + protected void init(Activity activity) { + + // Load our preferences from SharedPreferences + // If there's nothing there, we will end up returning a rather empty object + // to whoever called getInstance() and they can run the First Run Wizard + //preferences = context.getgetPreferences(0); // 0 == MODE_PRIVATE, but we don't extend Android's classes... + + // Load SharedPreferences + preferences = activity.getSharedPreferences(Dashboard.SHARED_PREFERENCES,Context.MODE_PRIVATE); + // Inflate our provider.json data + try { + definition = new JSONObject( preferences.getString(Provider.KEY, "") ); + } catch (JSONException e) { + // TODO: handle exception + + // FIXME!! We want "real" data!! + } + } + + protected String getDomain(){ + String domain = "Null"; + try { + domain = definition.getString(API_TERM_DOMAIN); + } catch (JSONException e) { + domain = "Null"; + e.printStackTrace(); + } + return domain; + } + + protected String getName(){ + // Should we pass the locale in, or query the system here? + String lang = Locale.getDefault().getLanguage(); + String name = "Null"; // Should it actually /be/ null, for error conditions? + try { + name = definition.getJSONObject(API_TERM_NAME).getString(lang); + } catch (JSONException e) { + // TODO: Nesting try/catch blocks? Crazy + // Maybe you should actually handle exception? + try { + name = definition.getJSONObject(API_TERM_NAME).getString( definition.getString(API_TERM_DEFAULT_LANGUAGE) ); + } catch (JSONException e2) { + // TODO: Will you handle the exception already? + } + } + + return name; + } + + protected String getDescription(){ + String lang = Locale.getDefault().getLanguage(); + String desc = null; + try { + desc = definition.getJSONObject("description").getString(lang); + } catch (JSONException e) { + // TODO: handle exception!! + try { + desc = definition.getJSONObject("description").getString( definition.getString("default_language") ); + } catch (JSONException e2) { + // TODO: i can't believe you're doing it again! + } + } + + return desc; + } + + protected boolean hasEIP() { + JSONArray services = null; + try { + services = definition.getJSONArray(API_TERM_SERVICES); // returns ["openvpn"] + } catch (Exception e) { + // TODO: handle exception + } + for (int i=0;i. + */ + package se.leap.bitmaskclient; + +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; + +/** + * Implements the ResultReceiver needed by Activities using ProviderAPI to receive the results of its operations. + * @author parmegv + * + */ +public class ProviderAPIResultReceiver extends ResultReceiver { + private Receiver mReceiver; + + public ProviderAPIResultReceiver(Handler handler) { + super(handler); + // TODO Auto-generated constructor stub + } + + public void setReceiver(Receiver receiver) { + mReceiver = receiver; + } + + /** + * Interface to enable ProviderAPIResultReceiver to receive results from the ProviderAPI IntentService. + * @author parmegv + * + */ + public interface Receiver { + public void onReceiveResult(int resultCode, Bundle resultData); + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (mReceiver != null) { + mReceiver.onReceiveResult(resultCode, resultData); + } + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java new file mode 100644 index 00000000..43bba085 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java @@ -0,0 +1,114 @@ +package se.leap.bitmaskclient; + +import java.util.List; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TwoLineListItem; + +public class ProviderListAdapter extends ArrayAdapter { + private static boolean[] hidden = null; + + public void hide(int position) { + hidden[getRealPosition(position)] = true; + notifyDataSetChanged(); + notifyDataSetInvalidated(); + } + + public void unHide(int position) { + hidden[getRealPosition(position)] = false; + notifyDataSetChanged(); + notifyDataSetInvalidated(); + } + + public void unHideAll() { + for (int provider_index = 0; provider_index < hidden.length; provider_index++) + hidden[provider_index] = false; + } + + private int getRealPosition(int position) { + int hElements = getHiddenCountUpTo(position); + int diff = 0; + for(int i=0;i objects) { + super(mContext, layout, objects); + if(hidden == null) { + hidden = new boolean[objects.size()]; + for (int i = 0; i < objects.size(); i++) + hidden[i] = false; + } + } + + public ProviderListAdapter(Context mContext, int layout, List objects, boolean show_all_providers) { + super(mContext, layout, objects); + if(show_all_providers) { + hidden = new boolean[objects.size()]; + for (int i = 0; i < objects.size(); i++) + hidden[i] = false; + } + } + + @Override + public void add(T item) { + super.add(item); + boolean[] new_hidden = new boolean[hidden.length+1]; + System.arraycopy(hidden, 0, new_hidden, 0, hidden.length); + new_hidden[hidden.length] = false; + hidden = new_hidden; + } + + @Override + public void remove(T item) { + super.remove(item); + boolean[] new_hidden = new boolean[hidden.length-1]; + System.arraycopy(hidden, 0, new_hidden, 0, hidden.length-1); + hidden = new_hidden; + } + + @Override + public View getView(int index, View convertView, ViewGroup parent) { + TwoLineListItem row; + int position = getRealPosition(index); + if (convertView == null) { + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + row = (TwoLineListItem)inflater.inflate(R.layout.provider_list_item, null); + } else { + row = (TwoLineListItem)convertView; + } + ProviderListContent.ProviderItem data = ProviderListContent.ITEMS.get(position); + row.getText1().setText(data.domain()); + row.getText2().setText(data.name()); + + return row; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java new file mode 100644 index 00000000..db414d87 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java @@ -0,0 +1,234 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; +import android.app.Activity; +import android.app.ListFragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +/** + * A list fragment representing a list of Providers. This fragment + * also supports tablet devices by allowing list items to be given an + * 'activated' state upon selection. This helps indicate which item is + * currently being viewed in a {@link DashboardFragment}. + *

+ * Activities containing this fragment MUST implement the {@link Callbacks} + * interface. + */ +public class ProviderListFragment extends ListFragment { + + public static String TAG = "provider_list_fragment"; + public static String SHOW_ALL_PROVIDERS = "show_all_providers"; + public static String TOP_PADDING = "top padding from providerlistfragment"; + private ProviderListAdapter content_adapter; + + /** + * The serialization (saved instance state) Bundle key representing the + * activated item position. Only used on tablets. + */ + private static final String STATE_ACTIVATED_POSITION = "activated_position"; + + /** + * The fragment's current callback object, which is notified of list item + * clicks. + */ + private Callbacks mCallbacks = sDummyCallbacks; + + /** + * The current activated item position. Only used on tablets. + */ + private int mActivatedPosition = ListView.INVALID_POSITION; + + /** + * A callback interface that all activities containing this fragment must + * implement. This mechanism allows activities to be notified of item + * selections. + */ + public interface Callbacks { + /** + * Callback for when an item has been selected. + */ + public void onItemSelected(String id); + } + + /** + * A dummy implementation of the {@link Callbacks} interface that does + * nothing. Used only when this fragment is not attached to an activity. + */ + private static Callbacks sDummyCallbacks = new Callbacks() { + @Override + public void onItemSelected(String id) { + } + }; + + /** + * Mandatory empty constructor for the fragment manager to instantiate the + * fragment (e.g. upon screen orientation changes). + */ + public ProviderListFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if(getArguments().containsKey(SHOW_ALL_PROVIDERS)) + content_adapter = new ProviderListAdapter( + getActivity(), + R.layout.provider_list_item, + ProviderListContent.ITEMS, getArguments().getBoolean(SHOW_ALL_PROVIDERS)); + else + content_adapter = new ProviderListAdapter( + getActivity(), + R.layout.provider_list_item, + ProviderListContent.ITEMS); + + + setListAdapter(content_adapter); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { + return inflater.inflate(R.layout.provider_list_fragment, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + // Restore the previously serialized activated item position. + if (savedInstanceState != null + && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) { + setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION)); + } + if(getArguments() != null && getArguments().containsKey(TOP_PADDING)) { + int topPadding = getArguments().getInt(TOP_PADDING); + View current_view = getView(); + getView().setPadding(current_view.getPaddingLeft(), topPadding, current_view.getPaddingRight(), current_view.getPaddingBottom()); + } + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + // Activities containing this fragment must implement its callbacks. + if (!(activity instanceof Callbacks)) { + throw new IllegalStateException("Activity must implement fragment's callbacks."); + } + + mCallbacks = (Callbacks) activity; + } + + @Override + public void onDetach() { + super.onDetach(); + + // Reset the active callbacks interface to the dummy implementation. + mCallbacks = sDummyCallbacks; + } + + @Override + public void onListItemClick(ListView listView, View view, int position, long id) { + super.onListItemClick(listView, view, position, id); + + // Notify the active callbacks interface (the activity, if the + // fragment is attached to one) that an item has been selected. + mCallbacks.onItemSelected(ProviderListContent.ITEMS.get(position).name()); + + for(int item_position = 0; item_position < listView.getCount(); item_position++) { + if(item_position != position) + content_adapter.hide(item_position); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (mActivatedPosition != ListView.INVALID_POSITION) { + // Serialize and persist the activated item position. + outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition); + } + } + + public void notifyAdapter() { + content_adapter.notifyDataSetChanged(); + } + /** + * Turns on activate-on-click mode. When this mode is on, list items will be + * given the 'activated' state when touched. + */ + public void setActivateOnItemClick(boolean activateOnItemClick) { + // When setting CHOICE_MODE_SINGLE, ListView will automatically + // give items the 'activated' state when touched. + getListView().setChoiceMode(activateOnItemClick + ? ListView.CHOICE_MODE_SINGLE + : ListView.CHOICE_MODE_NONE); + } + + private void setActivatedPosition(int position) { + if (position == ListView.INVALID_POSITION) { + getListView().setItemChecked(mActivatedPosition, false); + } else { + getListView().setItemChecked(position, true); + } + + mActivatedPosition = position; + } + + public void removeLastItem() { + unhideAll(); + content_adapter.remove(content_adapter.getItem(content_adapter.getCount()-1)); + content_adapter.notifyDataSetChanged(); + } + + public void addItem(ProviderItem provider) { + content_adapter.add(provider); + content_adapter.notifyDataSetChanged(); + } + + public void hideAllBut(int position) { + int real_count = content_adapter.getCount(); + for(int i = 0; i < real_count;) + if(i != position) { + content_adapter.hide(i); + position--; + real_count--; + } else { + i++; + } + } + + public void unhideAll() { + if(content_adapter != null) { + content_adapter.unHideAll(); + content_adapter.notifyDataSetChanged(); + } + } + + /** + * @return a new instance of this ListFragment. + */ + public static ProviderListFragment newInstance() { + return new ProviderListFragment(); + } +} diff --git a/app/src/main/java/se/leap/openvpn/CIDRIP.java b/app/src/main/java/se/leap/openvpn/CIDRIP.java new file mode 100644 index 00000000..8c4b6709 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/CIDRIP.java @@ -0,0 +1,58 @@ +package se.leap.openvpn; + +class CIDRIP{ + String mIp; + int len; + public CIDRIP(String ip, String mask){ + mIp=ip; + long netmask=getInt(mask); + + // Add 33. bit to ensure the loop terminates + netmask += 1l << 32; + + int lenZeros = 0; + while((netmask & 0x1) == 0) { + lenZeros++; + netmask = netmask >> 1; + } + // Check if rest of netmask is only 1s + if(netmask != (0x1ffffffffl >> lenZeros)) { + // Asume no CIDR, set /32 + len=32; + } else { + len =32 -lenZeros; + } + + } + @Override + public String toString() { + return String.format("%s/%d",mIp,len); + } + + public boolean normalise(){ + long ip=getInt(mIp); + + long newip = ip & (0xffffffffl << (32 -len)); + if (newip != ip){ + mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24,(newip & 0xff0000) >> 16, (newip & 0xff00) >> 8 ,newip & 0xff); + return true; + } else { + return false; + } + } + static long getInt(String ipaddr) { + String[] ipt = ipaddr.split("\\."); + long ip=0; + + ip += Long.parseLong(ipt[0])<< 24; + ip += Integer.parseInt(ipt[1])<< 16; + ip += Integer.parseInt(ipt[2])<< 8; + ip += Integer.parseInt(ipt[3]); + + return ip; + } + public long getInt() { + return getInt(mIp); + } + +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/openvpn/ConfigParser.java b/app/src/main/java/se/leap/openvpn/ConfigParser.java new file mode 100644 index 00000000..df4eae1b --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/ConfigParser.java @@ -0,0 +1,569 @@ +package se.leap.openvpn; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.HashMap; +import java.util.Locale; +import java.util.Vector; + +//! Openvpn Config FIle Parser, probably not 100% accurate but close enough + +// And rember, this is valid :) +// -- +// bar +// +public class ConfigParser { + + + private HashMap>> options = new HashMap>>(); + public void parseConfig(Reader reader) throws IOException, ConfigParseError { + + + BufferedReader br =new BufferedReader(reader); + + @SuppressWarnings("unused") + int lineno=0; + + while (true){ + String line = br.readLine(); + if(line==null) + break; + lineno++; + Vector args = parseline(line); + if(args.size() ==0) + continue; + + + if(args.get(0).startsWith("--")) + args.set(0, args.get(0).substring(2)); + + checkinlinefile(args,br); + + String optionname = args.get(0); + if(!options.containsKey(optionname)) { + options.put(optionname, new Vector>()); + } + options.get(optionname).add(args); + } + } + public void setDefinition(HashMap>> args) { + options = args; + } + + private void checkinlinefile(Vector args, BufferedReader br) throws IOException, ConfigParseError { + String arg0 = args.get(0); + // CHeck for + if(arg0.startsWith("<") && arg0.endsWith(">")) { + String argname = arg0.substring(1, arg0.length()-1); + String inlinefile = VpnProfile.INLINE_TAG; + + String endtag = String.format("",argname); + do { + String line = br.readLine(); + if(line==null){ + throw new ConfigParseError(String.format("No endtag for starttag <%s> found",argname,argname)); + } + if(line.equals(endtag)) + break; + else { + inlinefile+=line; + inlinefile+= "\n"; + } + } while(true); + + args.clear(); + args.add(argname); + args.add(inlinefile); + } + + } + + enum linestate { + initial, + readin_single_quote + , reading_quoted, reading_unquoted, done} + + private boolean space(char c) { + // I really hope nobody is using zero bytes inside his/her config file + // to sperate parameter but here we go: + return Character.isWhitespace(c) || c == '\0'; + + } + + public class ConfigParseError extends Exception { + private static final long serialVersionUID = -60L; + + public ConfigParseError(String msg) { + super(msg); + } + } + + + // adapted openvpn's parse function to java + private Vector parseline(String line) throws ConfigParseError { + Vector parameters = new Vector(); + + if (line.length()==0) + return parameters; + + + linestate state = linestate.initial; + boolean backslash = false; + char out=0; + + int pos=0; + String currentarg=""; + + do { + // Emulate the c parsing ... + char in; + if(pos < line.length()) + in = line.charAt(pos); + else + in = '\0'; + + if (!backslash && in == '\\' && state != linestate.readin_single_quote) + { + backslash = true; + } + else + { + if (state == linestate.initial) + { + if (!space (in)) + { + if (in == ';' || in == '#') /* comment */ + break; + if (!backslash && in == '\"') + state = linestate.reading_quoted; + else if (!backslash && in == '\'') + state = linestate.readin_single_quote; + else + { + out = in; + state = linestate.reading_unquoted; + } + } + } + else if (state == linestate.reading_unquoted) + { + if (!backslash && space (in)) + state = linestate.done; + else + out = in; + } + else if (state == linestate.reading_quoted) + { + if (!backslash && in == '\"') + state = linestate.done; + else + out = in; + } + else if (state == linestate.readin_single_quote) + { + if (in == '\'') + state = linestate.done; + else + out = in; + } + + if (state == linestate.done) + { + /* ASSERT (parm_len > 0); */ + state = linestate.initial; + parameters.add(currentarg); + currentarg = ""; + out =0; + } + + if (backslash && out!=0) + { + if (!(out == '\\' || out == '\"' || space (out))) + { + throw new ConfigParseError("Options warning: Bad backslash ('\\') usage"); + } + } + backslash = false; + } + + /* store parameter character */ + if (out!=0) + { + currentarg+=out; + } + } while (pos++ < line.length()); + + return parameters; + } + + + final String[] unsupportedOptions = { "config", + "connection", + "proto-force", + "remote-random", + "tls-server" + + }; + + // Ignore all scripts + // in most cases these won't work and user who wish to execute scripts will + // figure out themselves + final String[] ignoreOptions = { "tls-client", + "askpass", + "auth-nocache", + "up", + "down", + "route-up", + "ipchange", + "route-up", + "route-pre-down", + "auth-user-pass-verify", + "dhcp-release", + "dhcp-renew", + "dh", + "management-hold", + "management", + "management-query-passwords", + "pause-exit", + "persist-key", + "register-dns", + "route-delay", + "route-gateway", + "route-metric", + "route-method", + "status", + "script-security", + "show-net-up", + "suppress-timestamps", + "tmp-dir", + "tun-ipv6", + "topology", + "win-sys", + }; + + + // This method is far too long + public VpnProfile convertProfile() throws ConfigParseError{ + boolean noauthtypeset=true; + VpnProfile np = new VpnProfile("converted Profile"); + // Pull, client, tls-client + np.clearDefaults(); + + // XXX we are always client + if(/*options.containsKey("client") || options.containsKey("pull")*/ true) { + np.mUsePull=true; + options.remove("pull"); + options.remove("client"); + } + + Vector secret = getOption("secret", 1, 2); + if(secret!=null) + { + np.mAuthenticationType=VpnProfile.TYPE_STATICKEYS; + noauthtypeset=false; + np.mUseTLSAuth=true; + np.mTLSAuthFilename=secret.get(1); + if(secret.size()==3) + np.mTLSAuthDirection=secret.get(2); + + } + + Vector> routes = getAllOption("route", 1, 4); + if(routes!=null) { + String routeopt = ""; + for(Vector route:routes){ + String netmask = "255.255.255.255"; + if(route.size() >= 3) + netmask = route.get(2); + String net = route.get(1); + try { + CIDRIP cidr = new CIDRIP(net, netmask); + routeopt+=cidr.toString() + " "; + } catch (ArrayIndexOutOfBoundsException aioob) { + throw new ConfigParseError("Could not parse netmask of route " + netmask); + } catch (NumberFormatException ne) { + throw new ConfigParseError("Could not parse netmask of route " + netmask); + } + + } + np.mCustomRoutes=routeopt; + } + + // Also recognize tls-auth [inline] direction ... + Vector> tlsauthoptions = getAllOption("tls-auth", 1, 2); + if(tlsauthoptions!=null) { + for(Vector tlsauth:tlsauthoptions) { + if(tlsauth!=null) + { + if(!tlsauth.get(1).equals("[inline]")) { + np.mTLSAuthFilename=tlsauth.get(1); + np.mUseTLSAuth=true; + } + if(tlsauth.size()==3) + np.mTLSAuthDirection=tlsauth.get(2); + } + } + } + + Vector direction = getOption("key-direction", 1, 1); + if(direction!=null) + np.mTLSAuthDirection=direction.get(1); + + + if(getAllOption("redirect-gateway", 0, 5) != null) + np.mUseDefaultRoute=true; + + Vector dev =getOption("dev",1,1); + Vector devtype =getOption("dev-type",1,1); + + if( (devtype !=null && devtype.get(1).equals("tun")) || + (dev!=null && dev.get(1).startsWith("tun")) || + (devtype ==null && dev == null) ) { + //everything okay + } else { + throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail"); + } + + + + Vector mode =getOption("mode",1,1); + if (mode != null){ + if(!mode.get(1).equals("p2p")) + throw new ConfigParseError("Invalid mode for --mode specified, need p2p"); + } + + Vector port = getOption("port", 1,1); + if(port!=null){ + np.mServerPort = port.get(1); + } + + Vector proto = getOption("proto", 1,1); + if(proto!=null){ + np.mUseUdp=isUdpProto(proto.get(1));; + } + + // Parse remote config + Vector remote = getOption("remote",1,3); + if(remote != null){ + switch (remote.size()) { + case 4: + np.mUseUdp=isUdpProto(remote.get(3)); + case 3: + np.mServerPort = remote.get(2); + case 2: + np.mServerName = remote.get(1); + } + } + + // Parse remote config + Vector location = getOption("location",0,2); + if(location != null && location.size() == 2){ + np.mLocation = location.get(1).replace("__", ", "); + } + + Vector> dhcpoptions = getAllOption("dhcp-option", 2, 2); + if(dhcpoptions!=null) { + for(Vector dhcpoption:dhcpoptions) { + String type=dhcpoption.get(1); + String arg = dhcpoption.get(2); + if(type.equals("DOMAIN")) { + np.mSearchDomain=dhcpoption.get(2); + } else if(type.equals("DNS")) { + np.mOverrideDNS=true; + if(np.mDNS1.equals(VpnProfile.DEFAULT_DNS1)) + np.mDNS1=arg; + else + np.mDNS2=arg; + } + } + } + + Vector ifconfig = getOption("ifconfig", 2, 2); + if(ifconfig!=null) { + CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2)); + np.mIPv4Address=cidr.toString(); + } + + if(getOption("remote-random-hostname", 0, 0)!=null) + np.mUseRandomHostname=true; + + if(getOption("float", 0, 0)!=null) + np.mUseFloat=true; + + if(getOption("comp-lzo", 0, 1)!=null) + np.mUseLzo=true; + + Vector cipher = getOption("cipher", 1, 1); + if(cipher!=null) + np.mCipher= cipher.get(1); + + Vector ca = getOption("ca",1,1); + if(ca!=null){ + np.mCaFilename = ca.get(1); + } + + Vector cert = getOption("cert",1,1); + if(cert!=null){ + np.mClientCertFilename = cert.get(1); + np.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES; + noauthtypeset=false; + } + Vector key= getOption("key",1,1); + if(key!=null) + np.mClientKeyFilename=key.get(1); + + Vector pkcs12 = getOption("pkcs12",1,1); + if(pkcs12!=null) { + np.mPKCS12Filename = pkcs12.get(1); + np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE; + noauthtypeset=false; + } + + Vector tlsremote = getOption("tls-remote",1,1); + if(tlsremote!=null){ + np.mRemoteCN = tlsremote.get(1); + np.mCheckRemoteCN=true; + } + + Vector verb = getOption("verb",1,1); + if(verb!=null){ + np.mVerb=verb.get(1); + } + + + if(getOption("nobind", 0, 0) != null) + np.mNobind=true; + + if(getOption("persist-tun", 0,0) != null) + np.mPersistTun=true; + + Vector connectretry = getOption("connect-retry", 1, 1); + if(connectretry!=null) + np.mConnectRetry =connectretry.get(1); + + Vector connectretrymax = getOption("connect-retry-max", 1, 1); + if(connectretrymax!=null) + np.mConnectRetryMax =connectretrymax.get(1); + + Vector> remotetls = getAllOption("remote-cert-tls", 1, 1); + if(remotetls!=null) + if(remotetls.get(0).get(1).equals("server")) + np.mExpectTLSCert=true; + else + options.put("remotetls",remotetls); + + Vector authuser = getOption("auth-user-pass",0,1); + if(authuser !=null){ + if(noauthtypeset) { + np.mAuthenticationType=VpnProfile.TYPE_USERPASS; + } else if(np.mAuthenticationType==VpnProfile.TYPE_CERTIFICATES) { + np.mAuthenticationType=VpnProfile.TYPE_USERPASS_CERTIFICATES; + } else if(np.mAuthenticationType==VpnProfile.TYPE_KEYSTORE) { + np.mAuthenticationType=VpnProfile.TYPE_USERPASS_KEYSTORE; + } + if(authuser.size()>1) { + // Set option value to password get to get canche to embed later. + np.mUsername=null; + np.mPassword=authuser.get(1); + useEmbbedUserAuth(np,authuser.get(1)); + } + + } + + + // Check the other options + + checkIgnoreAndInvalidOptions(np); + fixup(np); + + return np; + } + + private boolean isUdpProto(String proto) throws ConfigParseError { + boolean isudp; + if(proto.equals("udp") || proto.equals("udp6")) + isudp=true; + else if (proto.equals("tcp-client") || + proto.equals("tcp") || + proto.equals("tcp6") || + proto.endsWith("tcp6-client")) + isudp =false; + else + throw new ConfigParseError("Unsupported option to --proto " + proto); + return isudp; + } + + static public void useEmbbedUserAuth(VpnProfile np,String inlinedata) + { + String data = inlinedata.replace(VpnProfile.INLINE_TAG, ""); + String[] parts = data.split("\n"); + if(parts.length >= 2) { + np.mUsername=parts[0]; + np.mPassword=parts[1]; + } + } + + private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError { + for(String option:unsupportedOptions) + if(options.containsKey(option)) + throw new ConfigParseError(String.format("Unsupported Option %s encountered in config file. Aborting",option)); + + for(String option:ignoreOptions) + // removing an item which is not in the map is no error + options.remove(option); + + if(options.size()> 0) { + String custom = "# These Options were found in the config file do not map to config settings:\n"; + + for(Vector> option:options.values()) { + for(Vector optionsline: option) { + for (String arg : optionsline) + custom+= VpnProfile.openVpnEscape(arg) + " "; + } + custom+="\n"; + + } + np.mCustomConfigOptions = custom; + np.mUseCustomConfig=true; + + } + } + + + private void fixup(VpnProfile np) { + if(np.mRemoteCN.equals(np.mServerName)) { + np.mRemoteCN=""; + } + } + + private Vector getOption(String option, int minarg, int maxarg) throws ConfigParseError { + Vector> alloptions = getAllOption(option, minarg, maxarg); + if(alloptions==null) + return null; + else + return alloptions.lastElement(); + } + + + private Vector> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError { + Vector> args = options.get(option); + if(args==null) + return null; + + for(Vector optionline:args) + + if(optionline.size()< (minarg+1) || optionline.size() > maxarg+1) { + String err = String.format(Locale.getDefault(),"Option %s has %d parameters, expected between %d and %d", + option,optionline.size()-1,minarg,maxarg ); + throw new ConfigParseError(err); + } + options.remove(option); + return args; + } + +} + + + + diff --git a/app/src/main/java/se/leap/openvpn/LICENSE.txt b/app/src/main/java/se/leap/openvpn/LICENSE.txt new file mode 100644 index 00000000..d897edea --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/LICENSE.txt @@ -0,0 +1,24 @@ +License for OpenVPN for Android. Please note that the thirdparty libraries/executables may have other license (OpenVPN, lzo, OpenSSL, Google Breakpad) + +Copyright (c) 2012-2013, Arne Schwabe + All rights reserved. + +If you need a non GPLv2 license of the source please contact me. + +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 2 +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, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +In addition, as a special exception, the copyright holders give +permission to link the code of portions of this program with the +OpenSSL library. diff --git a/app/src/main/java/se/leap/openvpn/LaunchVPN.java b/app/src/main/java/se/leap/openvpn/LaunchVPN.java new file mode 100644 index 00000000..89f2d372 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/LaunchVPN.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package se.leap.openvpn; + +import java.io.IOException; +import java.util.Collection; +import java.util.Vector; + +import se.leap.bitmaskclient.ConfigHelper; +import se.leap.bitmaskclient.EIP; +import se.leap.bitmaskclient.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ActivityNotFoundException; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.VpnService; +import android.os.Bundle; +import android.os.Parcelable; +import android.os.ResultReceiver; +import android.preference.PreferenceManager; +import android.text.InputType; +import android.text.method.PasswordTransformationMethod; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; + +/** + * This Activity actually handles two stages of a launcher shortcut's life cycle. + * + * 1. Your application offers to provide shortcuts to the launcher. When + * the user installs a shortcut, an activity within your application + * generates the actual shortcut and returns it to the launcher, where it + * is shown to the user as an icon. + * + * 2. Any time the user clicks on an installed shortcut, an intent is sent. + * Typically this would then be handled as necessary by an activity within + * your application. + * + * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form + * of an {@link android.content.Intent} that the launcher will use to create the shortcut. + * + * You can also implement this in an interactive way, by having your activity actually present + * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL, + * media item, or action. + * + * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents + * of the incoming {@link android.content.Intent}. + * + * In a real application, you would probably use the shortcut intent to display specific content + * or start a particular operation. + */ +public class LaunchVPN extends ListActivity implements OnItemClickListener { + + public static final String EXTRA_KEY = "se.leap.openvpn.shortcutProfileUUID"; + public static final String EXTRA_NAME = "se.leap.openvpn.shortcutProfileName"; + public static final String EXTRA_HIDELOG = "se.leap.openvpn.showNoLogWindow";; + + public static final int START_VPN_PROFILE= 70; + + // Dashboard, maybe more, want to know! + private ResultReceiver mReceiver; + + private ProfileManager mPM; + private VpnProfile mSelectedProfile; + private boolean mhideLog=false; + + private boolean mCmfixed=false; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mPM =ProfileManager.getInstance(this); + + } + + @Override + protected void onStart() { + super.onStart(); + // Resolve the intent + + final Intent intent = getIntent(); + final String action = intent.getAction(); + + // If something wants feedback, they sent us a Receiver + mReceiver = intent.getParcelableExtra(EIP.RECEIVER_TAG); + + // If the intent is a request to create a shortcut, we'll do that and exit + + + if(Intent.ACTION_MAIN.equals(action)) { + // we got called to be the starting point, most likely a shortcut + String shortcutUUID = intent.getStringExtra( EXTRA_KEY); + String shortcutName = intent.getStringExtra( EXTRA_NAME); + mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); + + VpnProfile profileToConnect = ProfileManager.get(shortcutUUID); + if(shortcutName != null && profileToConnect ==null) + profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName); + + if(profileToConnect ==null) { + OpenVPN.logError(R.string.shortcut_profile_notfound); + // show Log window to display error + showLogWindow(); + finish(); + return; + } + + mSelectedProfile = profileToConnect; + launchVPN(); + + } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) { + createListView(); + } + } + + private void createListView() { + ListView lv = getListView(); + //lv.setTextFilterEnabled(true); + + Collection vpnlist = mPM.getProfiles(); + + Vector vpnnames=new Vector(); + for (VpnProfile vpnProfile : vpnlist) { + vpnnames.add(vpnProfile.mName); + } + + + + ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,vpnnames); + lv.setAdapter(adapter); + + lv.setOnItemClickListener(this); + } + + /** + * This function creates a shortcut and returns it to the caller. There are actually two + * intents that you will send back. + * + * The first intent serves as a container for the shortcut and is returned to the launcher by + * setResult(). This intent must contain three fields: + * + *

    + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with + * the shortcut.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a + * bitmap, or {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as + * a drawable resource.
  • + *
+ * + * If you use a simple drawable resource, note that you must wrapper it using + * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so + * that the launcher can access resources that are stored in your application's .apk file. If + * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras + * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}. + * + * The shortcut intent can be any intent that you wish the launcher to send, when the user + * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW} + * with an appropriate Uri for your content, but any Intent will work here as long as it + * triggers the desired action within your Activity. + * @param profile + */ + private void setupShortcut(VpnProfile profile) { + // First, set up the shortcut intent. For this example, we simply create an intent that + // will bring us directly back to this activity. A more typical implementation would use a + // data Uri in order to display a more specific result, or a custom action in order to + // launch a specific operation. + + Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); + shortcutIntent.setClass(this, LaunchVPN.class); + shortcutIntent.putExtra(EXTRA_KEY,profile.getUUID().toString()); + + // Then, set up the container intent (the response to the caller) + + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName()); + Parcelable iconResource = Intent.ShortcutIconResource.fromContext( + this, R.drawable.icon); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); + + // Now, return the result to the launcher + + setResult(RESULT_OK, intent); + } + + + @Override + public void onItemClick(AdapterView parent, View view, int position, + long id) { + String profilename = ((TextView) view).getText().toString(); + + VpnProfile profile = mPM.getProfileByName(profilename); + + // if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) { + setupShortcut(profile); + finish(); + return; + // } + + } + + + + private void askForPW(final int type) { + + final EditText entry = new EditText(this); + entry.setSingleLine(); + entry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + entry.setTransformationMethod(new PasswordTransformationMethod()); + + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle("Need " + getString(type)); + dialog.setMessage("Enter the password for profile " + mSelectedProfile.mName); + dialog.setView(entry); + + dialog.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String pw = entry.getText().toString(); + if(type == R.string.password) { + mSelectedProfile.mTransientPW = pw; + } else { + mSelectedProfile.mTransientPCKS12PW = pw; + } + onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); + + } + + }); + dialog.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + + dialog.create().show(); + + } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if(requestCode==START_VPN_PROFILE) { + if(resultCode == Activity.RESULT_OK) { + int needpw = mSelectedProfile.needUserPWInput(); + if(needpw !=0) { + askForPW(needpw); + } else { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean showlogwindow = prefs.getBoolean("showlogwindow", false); + + if(!mhideLog && showlogwindow) + showLogWindow(); + new startOpenVpnThread().start(); + } + } else if (resultCode == Activity.RESULT_CANCELED) { + // User does not want us to start, so we just vanish (well, now we tell our receiver, then vanish) + Bundle resultData = new Bundle(); + // For now, nothing else is calling, so this "request" string is good enough + resultData.putString(EIP.REQUEST_TAG, EIP.ACTION_START_EIP); + mReceiver.send(RESULT_CANCELED, resultData); + finish(); + } + } + } + void showLogWindow() { + + Intent startLW = new Intent(getBaseContext(),LogWindow.class); + startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + startActivity(startLW); + + } + + void showConfigErrorDialog(int vpnok) { + AlertDialog.Builder d = new AlertDialog.Builder(this); + d.setTitle(R.string.config_error_found); + d.setMessage(vpnok); + d.setPositiveButton(android.R.string.ok, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + + } + }); + d.show(); + } + + void launchVPN () { + int vpnok = mSelectedProfile.checkProfile(this); + if(vpnok!= R.string.no_error_found) { + showConfigErrorDialog(vpnok); + return; + } + + Intent intent = VpnService.prepare(this); + // Check if we want to fix /dev/tun + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean usecm9fix = prefs.getBoolean("useCM9Fix", false); + boolean loadTunModule = prefs.getBoolean("loadTunModule", false); + + if(loadTunModule) + execeuteSUcmd("insmod /system/lib/modules/tun.ko"); + + if(usecm9fix && !mCmfixed ) { + execeuteSUcmd("chown system /dev/tun"); + } + + + if (intent != null) { + // Start the query + try { + startActivityForResult(intent, START_VPN_PROFILE); + } catch (ActivityNotFoundException ane) { + // Shame on you Sony! At least one user reported that + // an official Sony Xperia Arc S image triggers this exception + OpenVPN.logError(R.string.no_vpn_support_image); + showLogWindow(); + } + } else { + onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); + } + + } + + private void execeuteSUcmd(String command) { + ProcessBuilder pb = new ProcessBuilder(new String[] {"su","-c",command}); + try { + Process p = pb.start(); + int ret = p.waitFor(); + if(ret ==0) + mCmfixed=true; + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private class startOpenVpnThread extends Thread { + + @Override + public void run() { + VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); + // Tell whom-it-may-concern that we started VPN + Bundle resultData = new Bundle(); + // For now, nothing else is calling, so this "request" string is good enough + resultData.putString(EIP.REQUEST_TAG, EIP.ACTION_START_EIP); + mReceiver.send(RESULT_OK, resultData); + finish(); + + } + + } + + +} diff --git a/app/src/main/java/se/leap/openvpn/LogWindow.java b/app/src/main/java/se/leap/openvpn/LogWindow.java new file mode 100644 index 00000000..b87c4999 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/LogWindow.java @@ -0,0 +1,340 @@ +package se.leap.openvpn; + +import java.util.Vector; + +import se.leap.bitmaskclient.R; + +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.ListActivity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.database.DataSetObserver; +import android.os.Bundle; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Message; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; +import se.leap.openvpn.OpenVPN.LogItem; +import se.leap.openvpn.OpenVPN.LogListener; +import se.leap.openvpn.OpenVPN.StateListener; + +public class LogWindow extends ListActivity implements StateListener { + private static final int START_VPN_CONFIG = 0; + private String[] mBconfig=null; + + + class LogWindowListAdapter implements ListAdapter, LogListener, Callback { + + private static final int MESSAGE_NEWLOG = 0; + + private static final int MESSAGE_CLEARLOG = 1; + + private Vector myEntries=new Vector(); + + private Handler mHandler; + + private Vector observers=new Vector(); + + + public LogWindowListAdapter() { + initLogBuffer(); + + if (mHandler == null) { + mHandler = new Handler(this); + } + + OpenVPN.addLogListener(this); + } + + + + private void initLogBuffer() { + myEntries.clear(); + for (LogItem litem : OpenVPN.getlogbuffer()) { + myEntries.add(litem.getString(getContext())); + } + } + + String getLogStr() { + String str = ""; + for(String entry:myEntries) { + str+=entry + '\n'; + } + return str; + } + + + private void shareLog() { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_TEXT, getLogStr()); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.bitmask_openvpn_log_file)); + shareIntent.setType("text/plain"); + startActivity(Intent.createChooser(shareIntent, "Send Logfile")); + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + observers.add(observer); + + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + observers.remove(observer); + } + + @Override + public int getCount() { + return myEntries.size(); + } + + @Override + public Object getItem(int position) { + return myEntries.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView v; + if(convertView==null) + v = new TextView(getBaseContext()); + else + v = (TextView) convertView; + v.setText(myEntries.get(position)); + return v; + } + + @Override + public int getItemViewType(int position) { + return 0; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public boolean isEmpty() { + return myEntries.isEmpty(); + + } + + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEnabled(int position) { + return true; + } + + @Override + public void newLog(LogItem logmessage) { + Message msg = Message.obtain(); + msg.what=MESSAGE_NEWLOG; + Bundle mbundle=new Bundle(); + mbundle.putString("logmessage", logmessage.getString(getBaseContext())); + msg.setData(mbundle); + mHandler.sendMessage(msg); + } + + @Override + public boolean handleMessage(Message msg) { + // We have been called + if(msg.what==MESSAGE_NEWLOG) { + + String logmessage = msg.getData().getString("logmessage"); + myEntries.add(logmessage); + + for (DataSetObserver observer : observers) { + observer.onChanged(); + } + } else if (msg.what == MESSAGE_CLEARLOG) { + initLogBuffer(); + for (DataSetObserver observer : observers) { + observer.onInvalidated(); + } + } + + return true; + } + + void clearLog() { + // Actually is probably called from GUI Thread as result of the user + // pressing a button. But better safe than sorry + OpenVPN.clearLog(); + OpenVPN.logMessage(0,"","Log cleared."); + mHandler.sendEmptyMessage(MESSAGE_CLEARLOG); + } + } + + + + private LogWindowListAdapter ladapter; + private TextView mSpeedView; + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId()==R.id.clearlog) { + ladapter.clearLog(); + return true; + } else if(item.getItemId()==R.id.cancel){ + Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.title_cancel); + builder.setMessage(R.string.cancel_connection_query); + builder.setNegativeButton(android.R.string.no, null); + builder.setPositiveButton(android.R.string.yes, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + ProfileManager.setConntectedVpnProfileDisconnected(getApplicationContext()); + OpenVpnManagementThread.stopOpenVPN(); + } + }); + + builder.show(); + return true; + } else if(item.getItemId()==R.id.info) { + if(mBconfig==null) + OpenVPN.triggerLogBuilderConfig(); + + } else if(item.getItemId()==R.id.send) { + ladapter.shareLog(); + } + + return super.onOptionsItemSelected(item); + + } + + protected Context getContext() { + return this; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.logmenu, menu); + return true; + } + + + @Override + protected void onResume() { + super.onResume(); + OpenVPN.addStateListener(this); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == START_VPN_CONFIG && resultCode==RESULT_OK) { + String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + + final VpnProfile profile = ProfileManager.get(configuredVPN); + ProfileManager.getInstance(this).saveProfile(this, profile); + // Name could be modified, reset List adapter + + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle(R.string.configuration_changed); + dialog.setMessage(R.string.restart_vpn_after_change); + + + dialog.setPositiveButton(R.string.restart, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(getBaseContext(), LaunchVPN.class); + intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUIDString()); + intent.setAction(Intent.ACTION_MAIN); + startActivity(intent); + } + + + }); + dialog.setNegativeButton(R.string.ignore, null); + dialog.create().show(); + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onStop() { + super.onStop(); + OpenVPN.removeStateListener(this); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.logwindow); + ListView lv = getListView(); + + lv.setOnItemLongClickListener(new OnItemLongClickListener() { + + @Override + public boolean onItemLongClick(AdapterView parent, View view, + int position, long id) { + ClipboardManager clipboard = (ClipboardManager) + getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Log Entry",((TextView) view).getText()); + clipboard.setPrimaryClip(clip); + Toast.makeText(getBaseContext(), R.string.copied_entry, Toast.LENGTH_SHORT).show(); + return true; + } + }); + + ladapter = new LogWindowListAdapter(); + lv.setAdapter(ladapter); + + mSpeedView = (TextView) findViewById(R.id.speed); + } + + @Override + public void updateState(final String status,final String logmessage, final int resid) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + String prefix=getString(resid) + ":"; + if (status.equals("BYTECOUNT") || status.equals("NOPROCESS") ) + prefix=""; + mSpeedView.setText(prefix + logmessage); + } + }); + + } + + @Override + protected void onDestroy() { + super.onDestroy(); + OpenVPN.removeLogListener(ladapter); + } + +} diff --git a/app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java b/app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java new file mode 100644 index 00000000..777402b4 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/NetworkSateReceiver.java @@ -0,0 +1,86 @@ +package se.leap.openvpn; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.preference.PreferenceManager; +import se.leap.bitmaskclient.R; + +public class NetworkSateReceiver extends BroadcastReceiver { + private int lastNetwork=-1; + private OpenVpnManagementThread mManangement; + + private String lastStateMsg=null; + + public NetworkSateReceiver(OpenVpnManagementThread managementThread) { + super(); + mManangement = managementThread; + } + + @Override + public void onReceive(Context context, Intent intent) { + NetworkInfo networkInfo = getCurrentNetworkInfo(context); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean sendusr1 = prefs.getBoolean("netchangereconnect", true); + + String netstatestring; + if(networkInfo==null) + netstatestring = "not connected"; + else { + String subtype = networkInfo.getSubtypeName(); + if(subtype==null) + subtype = ""; + String extrainfo = networkInfo.getExtraInfo(); + if(extrainfo==null) + extrainfo=""; + + /* + if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) { + WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + WifiInfo wifiinfo = wifiMgr.getConnectionInfo(); + extrainfo+=wifiinfo.getBSSID(); + + subtype += wifiinfo.getNetworkId(); + }*/ + + + + netstatestring = String.format("%2$s %4$s to %1$s %3$s",networkInfo.getTypeName(), + networkInfo.getDetailedState(),extrainfo,subtype ); + } + + + + if(networkInfo!=null && networkInfo.getState() == State.CONNECTED) { + int newnet = networkInfo.getType(); + + if(sendusr1 && lastNetwork!=newnet) + mManangement.reconnect(); + + lastNetwork = newnet; + } else if (networkInfo==null) { + // Not connected, stop openvpn, set last connected network to no network + lastNetwork=-1; + if(sendusr1) + mManangement.signalusr1(); + } + + if(!netstatestring.equals(lastStateMsg)) + OpenVPN.logInfo(R.string.netstatus, netstatestring); + lastStateMsg=netstatestring; + + } + + private NetworkInfo getCurrentNetworkInfo(Context context) { + ConnectivityManager conn = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + + NetworkInfo networkInfo = conn.getActiveNetworkInfo(); + return networkInfo; + } + +} diff --git a/app/src/main/java/se/leap/openvpn/OpenVPN.java b/app/src/main/java/se/leap/openvpn/OpenVPN.java new file mode 100644 index 00000000..8acdc423 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/OpenVPN.java @@ -0,0 +1,250 @@ +package se.leap.openvpn; + +import java.util.LinkedList; +import java.util.Locale; +import java.util.Vector; + +import se.leap.bitmaskclient.R; + + +import android.content.Context; +import android.os.Build; +import android.util.Log; + +public class OpenVPN { + + + public static LinkedList logbuffer; + + private static Vector logListener; + private static Vector stateListener; + private static String[] mBconfig; + + private static String mLaststatemsg; + + private static String mLaststate; + + private static int mLastStateresid=R.string.state_noprocess; + public static String TAG="se.leap.openvpn.OpenVPN"; + + static { + logbuffer = new LinkedList(); + logListener = new Vector(); + stateListener = new Vector(); + logInformation(); + } + + public static class LogItem { + public static final int ERROR = 1; + public static final int INFO = 2; + public static final int VERBOSE = 3; + + private Object [] mArgs = null; + private String mMessage = null; + private int mRessourceId; + // Default log priority + int mLevel = INFO; + + public LogItem(int ressourceId, Object[] args) { + mRessourceId = ressourceId; + mArgs = args; + } + + + public LogItem(int loglevel,int ressourceId, Object[] args) { + mRessourceId = ressourceId; + mArgs = args; + mLevel = loglevel; + } + + + public LogItem(String message) { + + mMessage = message; + } + + public LogItem(int loglevel, String msg) { + mLevel = loglevel; + mMessage = msg; + } + + + public LogItem(int loglevel, int ressourceId) { + mRessourceId =ressourceId; + mLevel = loglevel; + } + + + public String getString(Context c) { + if(mMessage !=null) { + return mMessage; + } else { + if(c!=null) { + if(mArgs == null) + return c.getString(mRessourceId); + else + return c.getString(mRessourceId,mArgs); + } else { + String str = String.format(Locale.ENGLISH,"Log (no context) resid %d", mRessourceId); + if(mArgs !=null) + for(Object o:mArgs) + str += "|" + o.toString(); + return str; + } + } + } + } + + private static final int MAXLOGENTRIES = 500; + + + public static final String MANAGMENT_PREFIX = "M:"; + + + + + + + public interface LogListener { + void newLog(LogItem logItem); + } + + public interface StateListener { + void updateState(String state, String logmessage, int localizedResId); + } + + synchronized static void logMessage(int level,String prefix, String message) + { + newlogItem(new LogItem(prefix + message)); + Log.d("OpenVPN log item", message); + } + + synchronized static void clearLog() { + logbuffer.clear(); + logInformation(); + } + + private static void logInformation() { + + logInfo(R.string.mobile_info,Build.MODEL, Build.BOARD,Build.BRAND,Build.VERSION.SDK_INT); + } + + public synchronized static void addLogListener(LogListener ll){ + logListener.add(ll); + } + + public synchronized static void removeLogListener(LogListener ll) { + logListener.remove(ll); + } + + + public synchronized static void addStateListener(StateListener sl){ + stateListener.add(sl); + if(mLaststate!=null) + sl.updateState(mLaststate, mLaststatemsg, mLastStateresid); + } + + private static int getLocalizedState(String state){ + if (state.equals("CONNECTING")) + return R.string.state_connecting; + else if (state.equals("WAIT")) + return R.string.state_wait; + else if (state.equals("AUTH")) + return R.string.state_auth; + else if (state.equals("GET_CONFIG")) + return R.string.state_get_config; + else if (state.equals("ASSIGN_IP")) + return R.string.state_assign_ip; + else if (state.equals("ADD_ROUTES")) + return R.string.state_add_routes; + else if (state.equals("CONNECTED")) + return R.string.state_connected; + else if (state.equals("RECONNECTING")) + return R.string.state_reconnecting; + else if (state.equals("EXITING")) + return R.string.state_exiting; + else if (state.equals("RESOLVE")) + return R.string.state_resolve; + else if (state.equals("TCP_CONNECT")) + return R.string.state_tcp_connect; + else if (state.equals("FATAL")) + return R.string.eip_state_not_connected; + else + return R.string.unknown_state; + + } + + public synchronized static void removeStateListener(StateListener sl) { + stateListener.remove(sl); + } + + + synchronized public static LogItem[] getlogbuffer() { + + // The stoned way of java to return an array from a vector + // brought to you by eclipse auto complete + return (LogItem[]) logbuffer.toArray(new LogItem[logbuffer.size()]); + + } + public static void logBuilderConfig(String[] bconfig) { + mBconfig = bconfig; + } + public static void triggerLogBuilderConfig() { + if(mBconfig==null) { + logMessage(0, "", "No active interface"); + } else { + for (String item : mBconfig) { + logMessage(0, "", item); + } + } + + } + + public static void updateStateString (String state, String msg) { + int rid = getLocalizedState(state); + updateStateString(state, msg,rid); + } + + public synchronized static void updateStateString(String state, String msg, int resid) { + if (! "BYTECOUNT".equals(state)) { + mLaststate= state; + mLaststatemsg = msg; + mLastStateresid = resid; + + for (StateListener sl : stateListener) { + sl.updateState(state,msg,resid); + } + } + } + + public static void logInfo(String message) { + newlogItem(new LogItem(LogItem.INFO, message)); + } + + public static void logInfo(int ressourceId, Object... args) { + newlogItem(new LogItem(LogItem.INFO, ressourceId, args)); + } + + private static void newlogItem(LogItem logItem) { + logbuffer.addLast(logItem); + if(logbuffer.size()>MAXLOGENTRIES) + logbuffer.removeFirst(); + + for (LogListener ll : logListener) { + ll.newLog(logItem); + } + } + + public static void logError(String msg) { + newlogItem(new LogItem(LogItem.ERROR, msg)); + + } + + public static void logError(int ressourceId) { + newlogItem(new LogItem(LogItem.ERROR, ressourceId)); + } + public static void logError(int ressourceId, Object... args) { + newlogItem(new LogItem(LogItem.ERROR, ressourceId,args)); + } + +} diff --git a/app/src/main/java/se/leap/openvpn/OpenVPNThread.java b/app/src/main/java/se/leap/openvpn/OpenVPNThread.java new file mode 100644 index 00000000..ffd21732 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/OpenVPNThread.java @@ -0,0 +1,130 @@ +package se.leap.openvpn; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.LinkedList; + +import se.leap.bitmaskclient.R; + +import android.util.Log; +import se.leap.openvpn.OpenVPN.LogItem; + +public class OpenVPNThread implements Runnable { + private static final String DUMP_PATH_STRING = "Dump path: "; + private static final String TAG = "OpenVPN"; + private String[] mArgv; + private Process mProcess; + private String mNativeDir; + private OpenVpnService mService; + private String mDumpPath; + + public OpenVPNThread(OpenVpnService service,String[] argv, String nativelibdir) + { + mArgv = argv; + mNativeDir = nativelibdir; + mService = service; + } + + public void stopProcess() { + mProcess.destroy(); + } + + + + @Override + public void run() { + try { + Log.i(TAG, "Starting openvpn"); + startOpenVPNThreadArgs(mArgv); + Log.i(TAG, "Giving up"); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "OpenVPNThread Got " + e.toString()); + } finally { + int exitvalue = 0; + try { + exitvalue = mProcess.waitFor(); + } catch ( IllegalThreadStateException ite) { + OpenVPN.logError("Illegal Thread state: " + ite.getLocalizedMessage()); + } catch (InterruptedException ie) { + OpenVPN.logError("InterruptedException: " + ie.getLocalizedMessage()); + } + if( exitvalue != 0) + OpenVPN.logError("Process exited with exit value " + exitvalue); + +// OpenVPN.updateStateString("NOPROCESS","No process running.", R.string.state_noprocess); fixes bug #4565 + if(mDumpPath!=null) { + try { + BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log")); + for(LogItem li :OpenVPN.getlogbuffer()){ + logout.write(li.getString(null) + "\n"); + } + logout.close(); + OpenVPN.logError(R.string.minidump_generated); + } catch (IOException e) { + OpenVPN.logError("Writing minidump log: " +e.getLocalizedMessage()); + } + } + + mService.processDied(); + Log.i(TAG, "Exiting"); + } + } + + private void startOpenVPNThreadArgs(String[] argv) { + LinkedList argvlist = new LinkedList(); + + for(String arg:argv) + argvlist.add(arg); + + ProcessBuilder pb = new ProcessBuilder(argvlist); + // Hack O rama + + // Hack until I find a good way to get the real library path + String applibpath = argv[0].replace("/cache/" + VpnProfile.MINIVPN , "/lib"); + + String lbpath = pb.environment().get("LD_LIBRARY_PATH"); + if(lbpath==null) + lbpath = applibpath; + else + lbpath = lbpath + ":" + applibpath; + + if (!applibpath.equals(mNativeDir)) { + lbpath = lbpath + ":" + mNativeDir; + } + + pb.environment().put("LD_LIBRARY_PATH", lbpath); + pb.redirectErrorStream(true); + try { + mProcess = pb.start(); + // Close the output, since we don't need it + mProcess.getOutputStream().close(); + InputStream in = mProcess.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + while(true) { + String logline = br.readLine(); + if(logline==null) + return; + + if (logline.startsWith(DUMP_PATH_STRING)) + mDumpPath = logline.substring(DUMP_PATH_STRING.length()); + + + OpenVPN.logMessage(0, "P:", logline); + } + + + } catch (IOException e) { + OpenVPN.logMessage(0, "", "Error reading from output of OpenVPN process"+ e.getLocalizedMessage()); + e.printStackTrace(); + stopProcess(); + } + + + } +} diff --git a/app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java b/app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java new file mode 100644 index 00000000..27a3db65 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/OpenVpnManagementThread.java @@ -0,0 +1,592 @@ +package se.leap.openvpn; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.util.LinkedList; +import java.util.Vector; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +import se.leap.bitmaskclient.R; +import android.content.SharedPreferences; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.preference.PreferenceManager; +import android.util.Base64; +import android.util.Log; + +public class OpenVpnManagementThread implements Runnable { + + private static final String TAG = "openvpn"; + private LocalSocket mSocket; + private VpnProfile mProfile; + private OpenVpnService mOpenVPNService; + private LinkedList mFDList=new LinkedList(); + private int mBytecountinterval=2; + private long mLastIn=0; + private long mLastOut=0; + private LocalServerSocket mServerSocket; + private boolean mReleaseHold=true; + private boolean mWaitingForRelease=false; + private long mLastHoldRelease=0; + + private static Vector active=new Vector(); + + static private native void jniclose(int fdint); + static private native byte[] rsasign(byte[] input,int pkey) throws InvalidKeyException; + + public OpenVpnManagementThread(VpnProfile profile, LocalServerSocket mgmtsocket, OpenVpnService openVpnService) { + mProfile = profile; + mServerSocket = mgmtsocket; + mOpenVPNService = openVpnService; + + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(openVpnService); + boolean managemeNetworkState = prefs.getBoolean("netchangereconnect", true); + if(managemeNetworkState) + mReleaseHold=false; + + } + + static { + System.loadLibrary("opvpnutil"); + } + + public void managmentCommand(String cmd) { + if(mSocket!=null) { + try { + mSocket.getOutputStream().write(cmd.getBytes()); + mSocket.getOutputStream().flush(); + } catch (IOException e) { + // Ignore socket stack traces + } + } + } + + + @Override + public void run() { + Log.i(TAG, "Managment Socket Thread started"); + byte [] buffer =new byte[2048]; + // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad + + String pendingInput=""; + active.add(this); + + try { + // Wait for a client to connect + mSocket= mServerSocket.accept(); + InputStream instream = mSocket.getInputStream(); + + while(true) { + int numbytesread = instream.read(buffer); + if(numbytesread==-1) + return; + + FileDescriptor[] fds = null; + try { + fds = mSocket.getAncillaryFileDescriptors(); + } catch (IOException e) { + OpenVPN.logMessage(0, "", "Error reading fds from socket" + e.getLocalizedMessage()); + e.printStackTrace(); + } + if(fds!=null){ + + for (FileDescriptor fd : fds) { + + mFDList.add(fd); + } + } + + String input = new String(buffer,0,numbytesread,"UTF-8"); + + pendingInput += input; + + pendingInput=processInput(pendingInput); + + + + } + } catch (IOException e) { + e.printStackTrace(); + } + active.remove(this); + } + + //! Hack O Rama 2000! + private void protectFileDescriptor(FileDescriptor fd) { + Exception exp=null; + try { + Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$"); + int fdint = (Integer) getInt.invoke(fd); + + // You can even get more evil by parsing toString() and extract the int from that :) + + mOpenVPNService.protect(fdint); + + //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint); + //pfd.close(); + jniclose(fdint); + return; + } catch (NoSuchMethodException e) { + exp =e; + } catch (IllegalArgumentException e) { + exp =e; + } catch (IllegalAccessException e) { + exp =e; + } catch (InvocationTargetException e) { + exp =e; + } catch (NullPointerException e) { + exp =e; + } + if(exp!=null) { + exp.printStackTrace(); + Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd); + OpenVPN.logMessage(0, "", "Failed to retrieve fd from socket: " + exp.getLocalizedMessage()); + } + } + + private String processInput(String pendingInput) { + + + while(pendingInput.contains("\n")) { + String[] tokens = pendingInput.split("\\r?\\n", 2); + processCommand(tokens[0]); + if(tokens.length == 1) + // No second part, newline was at the end + pendingInput=""; + else + pendingInput=tokens[1]; + } + return pendingInput; + } + + + private void processCommand(String command) { + Log.d(TAG, "processCommand: " + command); + + if (command.startsWith(">") && command.contains(":")) { + String[] parts = command.split(":",2); + String cmd = parts[0].substring(1); + String argument = parts[1]; + if(cmd.equals("INFO")) { + // Ignore greeting from mgmt + //logStatusMessage(command); + }else if (cmd.equals("PASSWORD")) { + processPWCommand(argument); + } else if (cmd.equals("HOLD")) { + handleHold(); + } else if (cmd.equals("NEED-OK")) { + processNeedCommand(argument); + } else if (cmd.equals("BYTECOUNT")){ + processByteCount(argument); + } else if (cmd.equals("STATE")) { + processState(argument); + } else if (cmd.equals("FATAL")){ + processState(","+cmd+","); //handles FATAL as state + } else if (cmd.equals("PROXY")) { + processProxyCMD(argument); + } else if (cmd.equals("LOG")) { + String[] args = argument.split(",",3); + // 0 unix time stamp + // 1 log level N,I,E etc. + // 2 log message + OpenVPN.logMessage(0, "", args[2]); + } else if (cmd.equals("RSA_SIGN")) { + processSignCommand(argument); + } else { + OpenVPN.logMessage(0, "MGMT:", "Got unrecognized command" + command); + Log.i(TAG, "Got unrecognized command" + command); + } + } else if (command.startsWith("SUCCESS:")) { //Fixes bug LEAP #4565 + if (command.equals("SUCCESS: signal SIGINT thrown")){ + Log.d(TAG, "SUCCESS: signal SIGINT thrown"); + processState(",EXITING,SIGINT,,"); + } + } else { + Log.i(TAG, "Got unrecognized line from managment" + command); + OpenVPN.logMessage(0, "MGMT:", "Got unrecognized line from management:" + command); + } + } + private void handleHold() { + if(mReleaseHold) { + releaseHoldCmd(); + } else { + mWaitingForRelease=true; + OpenVPN.updateStateString("NONETWORK", "",R.string.state_nonetwork); + } + } + private void releaseHoldCmd() { + if ((System.currentTimeMillis()- mLastHoldRelease) < 5000) { + try { + Thread.sleep(3000); + } catch (InterruptedException e) {} + + } + mWaitingForRelease=false; + mLastHoldRelease = System.currentTimeMillis(); + managmentCommand("hold release\n"); + managmentCommand("bytecount " + mBytecountinterval + "\n"); + managmentCommand("state on\n"); + } + + public void releaseHold() { + mReleaseHold=true; + if(mWaitingForRelease) + releaseHoldCmd(); + + } + + private void processProxyCMD(String argument) { + String[] args = argument.split(",",3); + SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); + + + if(args.length >= 2) { + String proto = args[1]; + if(proto.equals("UDP")) { + proxyaddr=null; + } + } + + if(proxyaddr instanceof InetSocketAddress ){ + InetSocketAddress isa = (InetSocketAddress) proxyaddr; + + OpenVPN.logInfo(R.string.using_proxy, isa.getHostName(),isa.getPort()); + + String proxycmd = String.format("proxy HTTP %s %d\n", isa.getHostName(),isa.getPort()); + managmentCommand(proxycmd); + } else { + managmentCommand("proxy NONE\n"); + } + + } + private void processState(String argument) { + String[] args = argument.split(",",3); + String currentstate = args[1]; + if(args[2].equals(",,")){ + OpenVPN.updateStateString(currentstate,""); + } + else if (args[2].endsWith(",,")){ //fixes LEAP Bug #4546 + args[2] = (String) args[2].subSequence(0, args[2].length()-2); + Log.d(TAG, "processState() STATE: "+ currentstate + " msg: " + args[2]); + OpenVPN.updateStateString(currentstate,args[2]); + } + else{ + OpenVPN.updateStateString(currentstate,args[2]); + } + } + + private static int repeated_byte_counts = 0; + private void processByteCount(String argument) { + // >BYTECOUNT:{BYTES_IN},{BYTES_OUT} + int comma = argument.indexOf(','); + long in = Long.parseLong(argument.substring(0, comma)); + long out = Long.parseLong(argument.substring(comma+1)); + + long diffin = in - mLastIn; + long diffout = out - mLastOut; + if(diffin == 0 && diffout == 0) + repeated_byte_counts++; + if(repeated_byte_counts > 3) + Log.d("OpenVPN log", "Repeated byte count = " + repeated_byte_counts); + mLastIn=in; + mLastOut=out; + + String netstat = String.format("In: %8s, %8s/s Out %8s, %8s/s", + humanReadableByteCount(in, false), + humanReadableByteCount(diffin, false), + humanReadableByteCount(out, false), + humanReadableByteCount(diffout, false)); + OpenVPN.updateStateString("BYTECOUNT",netstat); + + + } + + // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java + public static String humanReadableByteCount(long bytes, boolean si) { + int unit = si ? 1000 : 1024; + if (bytes < unit) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); + } + + private void processNeedCommand(String argument) { + int p1 =argument.indexOf('\''); + int p2 = argument.indexOf('\'',p1+1); + + String needed = argument.substring(p1+1, p2); + String extra = argument.split(":",2)[1]; + + String status = "ok"; + + + if (needed.equals("PROTECTFD")) { + FileDescriptor fdtoprotect = mFDList.pollFirst(); + protectFileDescriptor(fdtoprotect); + } else if (needed.equals("DNSSERVER")) { + mOpenVPNService.addDNS(extra); + }else if (needed.equals("DNSDOMAIN")){ + mOpenVPNService.setDomain(extra); + } else if (needed.equals("ROUTE")) { + String[] routeparts = extra.split(" "); + mOpenVPNService.addRoute(routeparts[0], routeparts[1]); + } else if (needed.equals("ROUTE6")) { + mOpenVPNService.addRoutev6(extra); + } else if (needed.equals("IFCONFIG")) { + String[] ifconfigparts = extra.split(" "); + int mtu = Integer.parseInt(ifconfigparts[2]); + mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1],mtu,ifconfigparts[3]); + } else if (needed.equals("IFCONFIG6")) { + mOpenVPNService.setLocalIPv6(extra); + + } else if (needed.equals("OPENTUN")) { + if(sendTunFD(needed,extra)) + return; + else + status="cancel"; + // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :( + + } else { + Log.e(TAG,"Unkown needok command " + argument); + return; + } + + String cmd = String.format("needok '%s' %s\n", needed, status); + managmentCommand(cmd); + } + + private boolean sendTunFD (String needed, String extra) { + Exception exp = null; + if(!extra.equals("tun")) { + // We only support tun + String errmsg = String.format("Devicetype %s requested, but only tun is possible with the Android API, sorry!",extra); + OpenVPN.logMessage(0, "", errmsg ); + + return false; + } + ParcelFileDescriptor pfd = mOpenVPNService.openTun(); + if(pfd==null) + return false; + + Method setInt; + int fdint = pfd.getFd(); + try { + setInt = FileDescriptor.class.getDeclaredMethod("setInt$",int.class); + FileDescriptor fdtosend = new FileDescriptor(); + + setInt.invoke(fdtosend,fdint); + + FileDescriptor[] fds = {fdtosend}; + mSocket.setFileDescriptorsForSend(fds); + + Log.d("Openvpn", "Sending FD tosocket: " + fdtosend + " " + fdint + " " + pfd); + // Trigger a send so we can close the fd on our side of the channel + // The API documentation fails to mention that it will not reset the file descriptor to + // be send and will happily send the file descriptor on every write ... + String cmd = String.format("needok '%s' %s\n", needed, "ok"); + managmentCommand(cmd); + + // Set the FileDescriptor to null to stop this mad behavior + mSocket.setFileDescriptorsForSend(null); + + pfd.close(); + + return true; + } catch (NoSuchMethodException e) { + exp =e; + } catch (IllegalArgumentException e) { + exp =e; + } catch (IllegalAccessException e) { + exp =e; + } catch (InvocationTargetException e) { + exp =e; + } catch (IOException e) { + exp =e; + } + if(exp!=null) { + OpenVPN.logMessage(0,"", "Could not send fd over socket:" + exp.getLocalizedMessage()); + exp.printStackTrace(); + } + return false; + } + + private void processPWCommand(String argument) { + //argument has the form Need 'Private Key' password + // or ">PASSWORD:Verification Failed: '%s' ['%s']" + String needed; + + + + try{ + + int p1 = argument.indexOf('\''); + int p2 = argument.indexOf('\'',p1+1); + needed = argument.substring(p1+1, p2); + if (argument.startsWith("Verification Failed")) { + proccessPWFailed(needed, argument.substring(p2+1)); + return; + } + } catch (StringIndexOutOfBoundsException sioob) { + OpenVPN.logMessage(0, "", "Could not parse management Password command: " + argument); + return; + } + + String pw=null; + + if(needed.equals("Private Key")) { + pw = mProfile.getPasswordPrivateKey(); + } else if (needed.equals("Auth")) { + String usercmd = String.format("username '%s' %s\n", + needed, VpnProfile.openVpnEscape(mProfile.mUsername)); + managmentCommand(usercmd); + pw = mProfile.getPasswordAuth(); + } + if(pw!=null) { + String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); + managmentCommand(cmd); + } else { + OpenVPN.logMessage(0, OpenVPN.MANAGMENT_PREFIX, String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed)); + } + + } + + + + + private void proccessPWFailed(String needed, String args) { + OpenVPN.updateStateString("AUTH_FAILED", needed + args,R.string.state_auth_failed); + } + private void logStatusMessage(String command) { + OpenVPN.logMessage(0,"MGMT:", command); + } + + + public static boolean stopOpenVPN() { + boolean sendCMD=false; + for (OpenVpnManagementThread mt: active){ + mt.managmentCommand("signal SIGINT\n"); + sendCMD=true; + try { + if(mt.mSocket !=null) + mt.mSocket.close(); + } catch (IOException e) { + // Ignore close error on already closed socket + } + } + return sendCMD; + } + + public void signalusr1() { + mReleaseHold=false; + if(!mWaitingForRelease) + managmentCommand("signal SIGUSR1\n"); + } + + public void reconnect() { + signalusr1(); + releaseHold(); + } + + private void processSignCommand(String b64data) { + + PrivateKey privkey = mProfile.getKeystoreKey(); + Exception err =null; + + byte[] data = Base64.decode(b64data, Base64.DEFAULT); + + // The Jelly Bean *evil* Hack + // 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider + if(Build.VERSION.SDK_INT==16){ + processSignJellyBeans(privkey,data); + return; + } + + + try{ + + + Cipher rsasinger = Cipher.getInstance("RSA/ECB/PKCS1PADDING"); + + rsasinger.init(Cipher.ENCRYPT_MODE, privkey); + + byte[] signed_bytes = rsasinger.doFinal(data); + String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP); + managmentCommand("rsa-sig\n"); + managmentCommand(signed_string); + managmentCommand("\nEND\n"); + } catch (NoSuchAlgorithmException e){ + err =e; + } catch (InvalidKeyException e) { + err =e; + } catch (NoSuchPaddingException e) { + err =e; + } catch (IllegalBlockSizeException e) { + err =e; + } catch (BadPaddingException e) { + err =e; + } + if(err !=null) { + OpenVPN.logError(R.string.error_rsa_sign,err.getClass().toString(),err.getLocalizedMessage()); + } + + } + + + private void processSignJellyBeans(PrivateKey privkey, byte[] data) { + Exception err =null; + try { + Method[] allm = privkey.getClass().getSuperclass().getDeclaredMethods(); + System.out.println(allm); + Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey"); + getKey.setAccessible(true); + + // Real object type is OpenSSLKey + Object opensslkey = getKey.invoke(privkey); + + getKey.setAccessible(false); + + Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext"); + + // integer pointer to EVP_pkey + getPkeyContext.setAccessible(true); + int pkey = (Integer) getPkeyContext.invoke(opensslkey); + getPkeyContext.setAccessible(false); + + byte[] signed_bytes = rsasign(data, pkey); + String signed_string = Base64.encodeToString(signed_bytes, Base64.NO_WRAP); + managmentCommand("rsa-sig\n"); + managmentCommand(signed_string); + managmentCommand("\nEND\n"); + + } catch (NoSuchMethodException e) { + err=e; + } catch (IllegalArgumentException e) { + err=e; + } catch (IllegalAccessException e) { + err=e; + } catch (InvocationTargetException e) { + err=e; + } catch (InvalidKeyException e) { + err=e; + } + if(err !=null) { + OpenVPN.logError(R.string.error_rsa_sign,err.getClass().toString(),err.getLocalizedMessage()); + } + + } +} diff --git a/app/src/main/java/se/leap/openvpn/OpenVpnService.java b/app/src/main/java/se/leap/openvpn/OpenVpnService.java new file mode 100644 index 00000000..b5c9c798 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/OpenVpnService.java @@ -0,0 +1,504 @@ +package se.leap.openvpn; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Vector; + +import se.leap.bitmaskclient.Dashboard; +import se.leap.bitmaskclient.R; +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.net.VpnService; +import android.os.Binder; +import android.os.Handler.Callback; +import android.os.Build; +import android.os.IBinder; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import se.leap.openvpn.OpenVPN.StateListener; + +public class OpenVpnService extends VpnService implements StateListener, Callback { + public static final String START_SERVICE = "se.leap.openvpn.START_SERVICE"; + public static final String RETRIEVE_SERVICE = "se.leap.openvpn.RETRIEVE_SERVICE"; + + private Thread mProcessThread=null; + + private Vector mDnslist=new Vector(); + + private VpnProfile mProfile; + + private String mDomain=null; + + private Vector mRoutes=new Vector(); + private Vector mRoutesv6=new Vector(); + + private CIDRIP mLocalIP=null; + + private OpenVpnManagementThread mSocketManager; + + private Thread mSocketManagerThread; + private int mMtu; + private String mLocalIPv6=null; + private NetworkSateReceiver mNetworkStateReceiver; + private NotificationManager mNotificationManager; + + private boolean mDisplayBytecount=false; + + private boolean mStarting=false; + + private long mConnecttime; + + + private static final int OPENVPN_STATUS = 1; + + public static final int PROTECT_FD = 0; + + private final IBinder mBinder = new LocalBinder(); + + public class LocalBinder extends Binder { + public OpenVpnService getService() { + // Return this instance of LocalService so clients can call public methods + return OpenVpnService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + String action = intent.getAction(); + if( action !=null && (action.equals(START_SERVICE) || action.equals(RETRIEVE_SERVICE)) ) + return mBinder; + else + return super.onBind(intent); + } + + @Override + public void onRevoke() { + OpenVpnManagementThread.stopOpenVPN(); + endVpnService(); + } + + // Similar to revoke but do not try to stop process + public void processDied() { + endVpnService(); + } + + private void endVpnService() { + mProcessThread=null; + OpenVPN.logBuilderConfig(null); + ProfileManager.setConntectedVpnProfileDisconnected(this); + if(!mStarting) { + stopSelf(); + stopForeground(true); + } + } + + private void showNotification(String state, String msg, String tickerText, boolean lowpriority, long when, boolean persistant) { + String ns = Context.NOTIFICATION_SERVICE; + mNotificationManager = (NotificationManager) getSystemService(ns); + int icon; + if (state.equals("NOPROCESS") || state.equals("AUTH_FAILED") || state.equals("NONETWORK") || state.equals("EXITING")){ + icon = R.drawable.ic_vpn_disconnected; + }else{ + icon = R.drawable.ic_stat_vpn; + } + + android.app.Notification.Builder nbuilder = new Notification.Builder(this); + + nbuilder.setContentTitle(getString(R.string.notifcation_title,mProfile.mLocation)); + nbuilder.setContentText(msg); + nbuilder.setOnlyAlertOnce(true); + nbuilder.setOngoing(persistant); + nbuilder.setContentIntent(getLogPendingIntent()); + nbuilder.setSmallIcon(icon); + if(when !=0) + nbuilder.setWhen(when); + + + // Try to set the priority available since API 16 (Jellybean) + jbNotificationExtras(lowpriority, nbuilder); + if(tickerText!=null) + nbuilder.setTicker(tickerText); + + @SuppressWarnings("deprecation") + Notification notification = nbuilder.getNotification(); + + + mNotificationManager.notify(OPENVPN_STATUS, notification); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private void jbNotificationExtras(boolean lowpriority, + android.app.Notification.Builder nbuilder) { + try { + if(lowpriority) { + Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class); + // PRIORITY_MIN == -2 + setpriority.invoke(nbuilder, -2 ); + + nbuilder.setUsesChronometer(true); + /* PendingIntent cancelconnet=null; + + nbuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel, + getString(R.string.cancel_connection),cancelconnet); */ + } + + //ignore exception + } catch (NoSuchMethodException nsm) { + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + + } + + PendingIntent getLogPendingIntent() { + // Let the configure Button show the Dashboard + Intent intent = new Intent(Dashboard.getAppContext(),Dashboard.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + return startLW; + + } + + + private LocalServerSocket openManagmentInterface(int tries) { + // Could take a while to open connection + String socketname = (getCacheDir().getAbsolutePath() + "/" + "mgmtsocket"); + LocalSocket sock = new LocalSocket(); + + while(tries > 0 && !sock.isConnected()) { + try { + sock.bind(new LocalSocketAddress(socketname, + LocalSocketAddress.Namespace.FILESYSTEM)); + } catch (IOException e) { + // wait 300 ms before retrying + try { Thread.sleep(300); + } catch (InterruptedException e1) {} + + } + tries--; + } + + try { + LocalServerSocket lss = new LocalServerSocket(sock.getFileDescriptor()); + return lss; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + + + } + + void registerNetworkStateReceiver() { + // Registers BroadcastReceiver to track network connection changes. + IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); + mNetworkStateReceiver = new NetworkSateReceiver(mSocketManager); + this.registerReceiver(mNetworkStateReceiver, filter); + } + + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + + if( intent != null && intent.getAction() !=null && + (intent.getAction().equals(START_SERVICE) || intent.getAction().equals(RETRIEVE_SERVICE)) ) + return START_NOT_STICKY; + + + // Extract information from the intent. + String prefix = getPackageName(); + String[] argv = intent.getStringArrayExtra(prefix + ".ARGV"); + String nativelibdir = intent.getStringExtra(prefix + ".nativelib"); + String profileUUID = intent.getStringExtra(prefix + ".profileUUID"); + + mProfile = ProfileManager.get(profileUUID); + + //showNotification("Starting VPN " + mProfile.mName,"Starting VPN " + mProfile.mName, false,0); + + + OpenVPN.addStateListener(this); + + // Set a flag that we are starting a new VPN + mStarting=true; + // Stop the previous session by interrupting the thread. + if(OpenVpnManagementThread.stopOpenVPN()){ + // an old was asked to exit, wait 2s + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + } + + if (mProcessThread!=null) { + mProcessThread.interrupt(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + } + // An old running VPN should now be exited + mStarting=false; + + + // Open the Management Interface + LocalServerSocket mgmtsocket = openManagmentInterface(8); + + if(mgmtsocket!=null) { + // start a Thread that handles incoming messages of the managment socket + mSocketManager = new OpenVpnManagementThread(mProfile,mgmtsocket,this); + mSocketManagerThread = new Thread(mSocketManager,"OpenVPNMgmtThread"); + mSocketManagerThread.start(); + OpenVPN.logInfo("started Socket Thread"); + registerNetworkStateReceiver(); + } + + + // Start a new session by creating a new thread. + OpenVPNThread processThread = new OpenVPNThread(this, argv,nativelibdir); + + mProcessThread = new Thread(processThread, "OpenVPNProcessThread"); + mProcessThread.start(); + + ProfileManager.setConnectedVpnProfile(this, mProfile); + + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + if (mProcessThread != null) { + mSocketManager.managmentCommand("signal SIGINT\n"); + + mProcessThread.interrupt(); + } + if (mNetworkStateReceiver!= null) { + this.unregisterReceiver(mNetworkStateReceiver); + } + + } + + + + public ParcelFileDescriptor openTun() { + Builder builder = new Builder(); + + if(mLocalIP==null && mLocalIPv6==null) { + OpenVPN.logMessage(0, "", getString(R.string.opentun_no_ipaddr)); + return null; + } + + if(mLocalIP!=null) { + builder.addAddress(mLocalIP.mIp, mLocalIP.len); + } + + if(mLocalIPv6!=null) { + String[] ipv6parts = mLocalIPv6.split("/"); + builder.addAddress(ipv6parts[0],Integer.parseInt(ipv6parts[1])); + } + + + for (String dns : mDnslist ) { + try { + builder.addDnsServer(dns); + } catch (IllegalArgumentException iae) { + OpenVPN.logError(R.string.dns_add_error, dns,iae.getLocalizedMessage()); + } + } + + + builder.setMtu(mMtu); + + + for (CIDRIP route:mRoutes) { + try { + builder.addRoute(route.mIp, route.len); + } catch (IllegalArgumentException ia) { + OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage()); + } + } + + for(String v6route:mRoutesv6) { + try { + String[] v6parts = v6route.split("/"); + builder.addRoute(v6parts[0],Integer.parseInt(v6parts[1])); + } catch (IllegalArgumentException ia) { + OpenVPN.logMessage(0, "", getString(R.string.route_rejected) + v6route + " " + ia.getLocalizedMessage()); + } + } + + if(mDomain!=null) + builder.addSearchDomain(mDomain); + + String bconfig[] = new String[6]; + + bconfig[0]= getString(R.string.last_openvpn_tun_config); + bconfig[1] = getString(R.string.local_ip_info,mLocalIP.mIp,mLocalIP.len,mLocalIPv6, mMtu); + bconfig[2] = getString(R.string.dns_server_info, joinString(mDnslist)); + bconfig[3] = getString(R.string.dns_domain_info, mDomain); + bconfig[4] = getString(R.string.routes_info, joinString(mRoutes)); + bconfig[5] = getString(R.string.routes_info6, joinString(mRoutesv6)); + + String session = mProfile.mLocation; + /* we don't want the IP address in the notification bar + if(mLocalIP!=null && mLocalIPv6!=null) + session = getString(R.string.session_ipv6string,session, mLocalIP, mLocalIPv6); + else if (mLocalIP !=null) + session= getString(R.string.session_ipv4string, session, mLocalIP); + */ + builder.setSession(session); + + + OpenVPN.logBuilderConfig(bconfig); + + // No DNS Server, log a warning + if(mDnslist.size()==0) + OpenVPN.logInfo(R.string.warn_no_dns); + + // Reset information + mDnslist.clear(); + mRoutes.clear(); + mRoutesv6.clear(); + mLocalIP=null; + mLocalIPv6=null; + mDomain=null; + + builder.setConfigureIntent(getLogPendingIntent()); + + try { + ParcelFileDescriptor pfd = builder.establish(); + return pfd; + } catch (Exception e) { + OpenVPN.logMessage(0, "", getString(R.string.tun_open_error)); + OpenVPN.logMessage(0, "", getString(R.string.error) + e.getLocalizedMessage()); + OpenVPN.logMessage(0, "", getString(R.string.tun_error_helpful)); + return null; + } + + } + + + // Ugly, but java has no such method + private String joinString(Vector vec) { + String ret = ""; + if(vec.size() > 0){ + ret = vec.get(0).toString(); + for(int i=1;i < vec.size();i++) { + ret = ret + ", " + vec.get(i).toString(); + } + } + return ret; + } + + + + + + + public void addDNS(String dns) { + mDnslist.add(dns); + } + + + public void setDomain(String domain) { + if(mDomain==null) { + mDomain=domain; + } + } + + + public void addRoute(String dest, String mask) { + CIDRIP route = new CIDRIP(dest, mask); + if(route.len == 32 && !mask.equals("255.255.255.255")) { + OpenVPN.logMessage(0, "", getString(R.string.route_not_cidr,dest,mask)); + } + + if(route.normalise()) + OpenVPN.logMessage(0, "", getString(R.string.route_not_netip,dest,route.len,route.mIp)); + + mRoutes.add(route); + } + + public void addRoutev6(String extra) { + mRoutesv6.add(extra); + } + + + public void setLocalIP(String local, String netmask,int mtu, String mode) { + mLocalIP = new CIDRIP(local, netmask); + mMtu = mtu; + + if(mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) { + // get the netmask as IP + long netint = CIDRIP.getInt(netmask); + if(Math.abs(netint - mLocalIP.getInt()) ==1) { + if(mode.equals("net30")) + mLocalIP.len=30; + else + mLocalIP.len=31; + } else { + OpenVPN.logMessage(0, "", getString(R.string.ip_not_cidr, local,netmask,mode)); + } + } + } + + public void setLocalIPv6(String ipv6addr) { + mLocalIPv6 = ipv6addr; + } + + public boolean isRunning() { + if (mStarting == true || mProcessThread != null) + return true; + else + return false; + } + + @Override + public void updateState(String state,String logmessage, int resid) { + // If the process is not running, ignore any state, + // Notification should be invisible in this state + if(mProcessThread==null) + return; + if("CONNECTED".equals(state)) { + mNotificationManager.cancel(OPENVPN_STATUS); + } else if(!"BYTECOUNT".equals(state)) { + + // Other notifications are shown, + // This also mean we are no longer connected, ignore bytecount messages until next + // CONNECTED + String ticker = getString(resid); + boolean persist = false; + if (("NOPROCESS".equals(state) ) || ("EXITING").equals(state)){ + showNotification(state, getString(R.string.eip_state_not_connected), ticker, false, 0, persist); + } + else if (state.equals("GET_CONFIG") || state.equals("ASSIGN_IP")){ //don't show them in the notification message + } + else{ + persist = true; + showNotification(state, getString(resid) +" " + logmessage,ticker,false,0,persist); + } + } + } + + @Override + public boolean handleMessage(Message msg) { + Runnable r = msg.getCallback(); + if(r!=null){ + r.run(); + return true; + } else { + return false; + } + } +} diff --git a/app/src/main/java/se/leap/openvpn/ProfileManager.java b/app/src/main/java/se/leap/openvpn/ProfileManager.java new file mode 100644 index 00000000..b9eb3ab6 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/ProfileManager.java @@ -0,0 +1,220 @@ +package se.leap.openvpn; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.StreamCorruptedException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.preference.PreferenceManager; + +public class ProfileManager { + private static final String PREFS_NAME = "VPNList"; + + + + private static final String ONBOOTPROFILE = "onBootProfile"; + + + + private static ProfileManager instance; + + + + private static VpnProfile mLastConnectedVpn=null; + private HashMap profiles=new HashMap(); + private static VpnProfile tmpprofile=null; + + + public static VpnProfile get(String key) { + if (tmpprofile!=null && tmpprofile.getUUIDString().equals(key)) + return tmpprofile; + + if(instance==null) + return null; + return instance.profiles.get(key); + + } + + + + private ProfileManager() { } + + private static void checkInstance(Context context) { + if(instance == null) { + instance = new ProfileManager(); + instance.loadVPNList(context); + } + } + + synchronized public static ProfileManager getInstance(Context context) { + checkInstance(context); + return instance; + } + + public static void setConntectedVpnProfileDisconnected(Context c) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + Editor prefsedit = prefs.edit(); + prefsedit.putString(ONBOOTPROFILE, null); + prefsedit.apply(); + + } + + public static void setConnectedVpnProfile(Context c, VpnProfile connectedrofile) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + Editor prefsedit = prefs.edit(); + + prefsedit.putString(ONBOOTPROFILE, connectedrofile.getUUIDString()); + prefsedit.apply(); + mLastConnectedVpn=connectedrofile; + + } + + public static VpnProfile getOnBootProfile(Context c) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); + + boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false); + + + String mBootProfileUUID = prefs.getString(ONBOOTPROFILE,null); + if(useStartOnBoot && mBootProfileUUID!=null) + return get(c, mBootProfileUUID); + else + return null; + } + + + + + public Collection getProfiles() { + return profiles.values(); + } + + public VpnProfile getProfileByName(String name) { + for (VpnProfile vpnp : profiles.values()) { + if(vpnp.getName().equals(name)) { + return vpnp; + } + } + return null; + } + + public void saveProfileList(Context context) { + SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); + Editor editor = sharedprefs.edit(); + editor.putStringSet("vpnlist", profiles.keySet()); + + // For reasing I do not understand at all + // Android saves my prefs file only one time + // if I remove the debug code below :( + int counter = sharedprefs.getInt("counter", 0); + editor.putInt("counter", counter+1); + editor.apply(); + + } + + public void addProfile(VpnProfile profile) { + profiles.put(profile.getUUID().toString(),profile); + + } + + public static void setTemporaryProfile(VpnProfile tmp) { + ProfileManager.tmpprofile = tmp; + } + + + public void saveProfile(Context context,VpnProfile profile) { + // First let basic settings save its state + + ObjectOutputStream vpnfile; + try { + vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"),Activity.MODE_PRIVATE)); + + vpnfile.writeObject(profile); + vpnfile.flush(); + vpnfile.close(); + } catch (FileNotFoundException e) { + + e.printStackTrace(); + throw new RuntimeException(e); + } catch (IOException e) { + + e.printStackTrace(); + throw new RuntimeException(e); + + } + } + + + private void loadVPNList(Context context) { + profiles = new HashMap(); + SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME,Activity.MODE_PRIVATE); + Set vlist = listpref.getStringSet("vpnlist", null); + Exception exp =null; + if(vlist==null){ + vlist = new HashSet(); + } + + for (String vpnentry : vlist) { + try { + ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); + VpnProfile vp = ((VpnProfile) vpnfile.readObject()); + + // Sanity check + if(vp==null || vp.mName==null || vp.getUUID()==null) + continue; + + profiles.put(vp.getUUID().toString(), vp); + + } catch (StreamCorruptedException e) { + exp=e; + } catch (FileNotFoundException e) { + exp=e; + } catch (IOException e) { + exp=e; + } catch (ClassNotFoundException e) { + exp=e; + } + if(exp!=null) { + exp.printStackTrace(); + } + } + } + + public int getNumberOfProfiles() { + return profiles.size(); + } + + + + public void removeProfile(Context context,VpnProfile profile) { + String vpnentry = profile.getUUID().toString(); + profiles.remove(vpnentry); + saveProfileList(context); + context.deleteFile(vpnentry + ".vp"); + if(mLastConnectedVpn==profile) + mLastConnectedVpn=null; + + } + + + + public static VpnProfile get(Context context, String profileUUID) { + checkInstance(context); + return get(profileUUID); + } + + + + public static VpnProfile getLastConnectedVpn() { + return mLastConnectedVpn; + } + +} diff --git a/app/src/main/java/se/leap/openvpn/ProxyDetection.java b/app/src/main/java/se/leap/openvpn/ProxyDetection.java new file mode 100644 index 00000000..c7b3d196 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/ProxyDetection.java @@ -0,0 +1,54 @@ +package se.leap.openvpn; + +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; + +import se.leap.bitmaskclient.R; + +public class ProxyDetection { + static SocketAddress detectProxy(VpnProfile vp) { + // Construct a new url with https as protocol + try { + URL url = new URL(String.format("https://%s:%s",vp.mServerName,vp.mServerPort)); + Proxy proxy = getFirstProxy(url); + + if(proxy==null) + return null; + SocketAddress addr = proxy.address(); + if (addr instanceof InetSocketAddress) { + return addr; + } + + } catch (MalformedURLException e) { + OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage()); + } catch (URISyntaxException e) { + OpenVPN.logError(R.string.getproxy_error,e.getLocalizedMessage()); + } + return null; + } + + static Proxy getFirstProxy(URL url) throws URISyntaxException { + System.setProperty("java.net.useSystemProxies", "true"); + + List proxylist = ProxySelector.getDefault().select(url.toURI()); + + + if (proxylist != null) { + for (Proxy proxy: proxylist) { + SocketAddress addr = proxy.address(); + + if (addr != null) { + return proxy; + } + } + + } + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java b/app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java new file mode 100644 index 00000000..418cf7e9 --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/VPNLaunchHelper.java @@ -0,0 +1,76 @@ +package se.leap.openvpn; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import se.leap.bitmaskclient.R; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +public class VPNLaunchHelper { + static private boolean writeMiniVPN(Context context) { + File mvpnout = new File(context.getCacheDir(),VpnProfile.MINIVPN); + if (mvpnout.exists() && mvpnout.canExecute()) + return true; + + IOException e2 = null; + + try { + InputStream mvpn; + + try { + mvpn = context.getAssets().open("minivpn." + Build.CPU_ABI); + } + catch (IOException errabi) { + OpenVPN.logInfo("Failed getting assets for archicture " + Build.CPU_ABI); + e2=errabi; + mvpn = context.getAssets().open("minivpn." + Build.CPU_ABI2); + + } + + + FileOutputStream fout = new FileOutputStream(mvpnout); + + byte buf[]= new byte[4096]; + + int lenread = mvpn.read(buf); + while(lenread> 0) { + fout.write(buf, 0, lenread); + lenread = mvpn.read(buf); + } + fout.close(); + + if(!mvpnout.setExecutable(true)) { + OpenVPN.logMessage(0, "","Failed to set minivpn executable"); + return false; + } + + + return true; + } catch (IOException e) { + if(e2!=null) + OpenVPN.logMessage(0, "",e2.getLocalizedMessage()); + OpenVPN.logMessage(0, "",e.getLocalizedMessage()); + e.printStackTrace(); + return false; + } + } + + + public static void startOpenVpn(VpnProfile startprofile, Context context) { + if(!writeMiniVPN(context)) { + OpenVPN.logMessage(0, "", "Error writing minivpn binary"); + return; + } + OpenVPN.logMessage(0, "", context.getString(R.string.building_configration)); + + Intent startVPN = startprofile.prepareIntent(context); + if(startVPN!=null) + context.startService(startVPN); + + } +} diff --git a/app/src/main/java/se/leap/openvpn/VpnProfile.java b/app/src/main/java/se/leap/openvpn/VpnProfile.java new file mode 100644 index 00000000..481819ad --- /dev/null +++ b/app/src/main/java/se/leap/openvpn/VpnProfile.java @@ -0,0 +1,758 @@ +package se.leap.openvpn; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.UUID; +import java.util.Vector; + +import org.spongycastle.util.io.pem.PemObject; +import org.spongycastle.util.io.pem.PemWriter; + +import se.leap.bitmaskclient.ConfigHelper; +import se.leap.bitmaskclient.Dashboard; +import se.leap.bitmaskclient.EIP; +import se.leap.bitmaskclient.Provider; +import se.leap.bitmaskclient.R; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.preference.PreferenceManager; +import android.security.KeyChain; +import android.security.KeyChainException; + +public class VpnProfile implements Serializable{ + // Parcable + /** + * + */ + private static final long serialVersionUID = 7085688938959334563L; + static final int TYPE_CERTIFICATES=0; + static final int TYPE_PKCS12=1; + static final int TYPE_KEYSTORE=2; + public static final int TYPE_USERPASS = 3; + public static final int TYPE_STATICKEYS = 4; + public static final int TYPE_USERPASS_CERTIFICATES = 5; + public static final int TYPE_USERPASS_PKCS12 = 6; + public static final int TYPE_USERPASS_KEYSTORE = 7; + + // Don't change this, not all parts of the program use this constant + public static final String EXTRA_PROFILEUUID = "se.leap.bitmaskclient.profileUUID"; // TODO this feels wrong. See Issue #1494 + public static final String INLINE_TAG = "[[INLINE]]"; + private static final String OVPNCONFIGFILE = "android.conf"; + + protected transient String mTransientPW=null; + protected transient String mTransientPCKS12PW=null; + private transient PrivateKey mPrivateKey; + protected boolean profileDleted=false; + + + public static String DEFAULT_DNS1="131.234.137.23"; + public static String DEFAULT_DNS2="131.234.137.24"; + + // Public attributes, since I got mad with getter/setter + // set members to default values + private UUID mUuid; + public int mAuthenticationType = TYPE_CERTIFICATES ; + public String mName; + public String mLocation; + public String mAlias; + public String mClientCertFilename; + public String mTLSAuthDirection=""; + public String mTLSAuthFilename; + public String mClientKeyFilename; + public String mCaFilename; + public boolean mUseLzo=true; + public String mServerPort= "1194" ; + public boolean mUseUdp = true; + public String mPKCS12Filename; + public String mPKCS12Password; + public boolean mUseTLSAuth = false; + public String mServerName = "openvpn.blinkt.de" ; + public String mDNS1=DEFAULT_DNS1; + public String mDNS2=DEFAULT_DNS2; + public String mIPv4Address; + public String mIPv6Address; + public boolean mOverrideDNS=false; + public String mSearchDomain="blinkt.de"; + public boolean mUseDefaultRoute=true; + public boolean mUsePull=true; + public String mCustomRoutes; + public boolean mCheckRemoteCN=false; + public boolean mExpectTLSCert=true; + public String mRemoteCN=""; + public String mPassword=""; + public String mUsername=""; + public boolean mRoutenopull=false; + public boolean mUseRandomHostname=false; + public boolean mUseFloat=false; + public boolean mUseCustomConfig=false; + public String mCustomConfigOptions=""; + public String mVerb="1"; + public String mCipher=""; + public boolean mNobind=false; + public boolean mUseDefaultRoutev6=true; + public String mCustomRoutesv6=""; + public String mKeyPassword=""; + public boolean mPersistTun = false; + public String mConnectRetryMax="5"; + public String mConnectRetry="10"; + public boolean mUserEditable=true; + + static final String MINIVPN = "miniopenvpn"; + + + + + + + public void clearDefaults() { + mServerName="unkown"; + mUsePull=false; + mUseLzo=false; + mUseDefaultRoute=false; + mUseDefaultRoutev6=false; + mExpectTLSCert=false; + mPersistTun = false; + } + + + public static String openVpnEscape(String unescaped) { + if(unescaped==null) + return null; + String escapedString = unescaped.replace("\\", "\\\\"); + escapedString = escapedString.replace("\"","\\\""); + escapedString = escapedString.replace("\n","\\n"); + + if (escapedString.equals(unescaped) && !escapedString.contains(" ") && !escapedString.contains("#")) + return unescaped; + else + return '"' + escapedString + '"'; + } + + + static final String OVPNCONFIGCA = "android-ca.pem"; + static final String OVPNCONFIGUSERCERT = "android-user.pem"; + + + public VpnProfile(String name) { + mUuid = UUID.randomUUID(); + mName = name; + } + + public UUID getUUID() { + return mUuid; + + } + + public String getName() { + return mName; + } + + + public String getConfigFile(Context context) + { + + File cacheDir= context.getCacheDir(); + String cfg=""; + + // Enable managment interface + cfg += "# Enables connection to GUI\n"; + cfg += "management "; + + cfg +=cacheDir.getAbsolutePath() + "/" + "mgmtsocket"; + cfg += " unix\n"; + cfg += "management-client\n"; + // Not needed, see updated man page in 2.3 + //cfg += "management-signal\n"; + cfg += "management-query-passwords\n"; + cfg += "management-hold\n\n"; + + /* tmp-dir patched out :) + cfg+="# /tmp does not exist on Android\n"; + cfg+="tmp-dir "; + cfg+=cacheDir.getAbsolutePath(); + cfg+="\n\n"; */ + + cfg+="# Log window is better readable this way\n"; + cfg+="suppress-timestamps\n"; + + + + boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS); + + if(useTLSClient && mUsePull) + cfg+="client\n"; + else if (mUsePull) + cfg+="pull\n"; + else if(useTLSClient) + cfg+="tls-client\n"; + + + cfg+="verb " + mVerb + "\n"; + + if(mConnectRetryMax ==null) { + mConnectRetryMax="5"; + } + + if(!mConnectRetryMax.equals("-1")) + cfg+="connect-retry-max " + mConnectRetryMax+ "\n"; + + if(mConnectRetry==null) + mConnectRetry="10"; + + + cfg+="connect-retry " + mConnectRetry + "\n"; + + cfg+="resolv-retry 60\n"; + + + + // We cannot use anything else than tun + cfg+="dev tun\n"; + + // Server Address + cfg+="remote "; + cfg+=mServerName; + cfg+=" "; + cfg+=mServerPort; + if(mUseUdp) + cfg+=" udp\n"; + else + cfg+=" tcp-client\n"; + + + + + switch(mAuthenticationType) { + case VpnProfile.TYPE_USERPASS_CERTIFICATES: + cfg+="auth-user-pass\n"; + case VpnProfile.TYPE_CERTIFICATES: + /*// Ca + cfg+=insertFileData("ca",mCaFilename); + + // Client Cert + Key + cfg+=insertFileData("key",mClientKeyFilename); + cfg+=insertFileData("cert",mClientCertFilename); +*/ + // FIXME This is all we need...The whole switch statement can go... + SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, context.MODE_PRIVATE); + cfg+="\n"+preferences.getString(Provider.CA_CERT, "")+"\n\n"; + cfg+="\n"+preferences.getString(EIP.PRIVATE_KEY, "")+"\n\n"; + cfg+="\n"+preferences.getString(EIP.CERTIFICATE, "")+"\n\n"; + + break; + case VpnProfile.TYPE_USERPASS_PKCS12: + cfg+="auth-user-pass\n"; + case VpnProfile.TYPE_PKCS12: + cfg+=insertFileData("pkcs12",mPKCS12Filename); + break; + + case VpnProfile.TYPE_USERPASS_KEYSTORE: + cfg+="auth-user-pass\n"; + case VpnProfile.TYPE_KEYSTORE: + cfg+="ca " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGCA + "\n"; + cfg+="cert " + cacheDir.getAbsolutePath() + "/" + OVPNCONFIGUSERCERT + "\n"; + cfg+="management-external-key\n"; + + break; + case VpnProfile.TYPE_USERPASS: + cfg+="auth-user-pass\n"; + cfg+=insertFileData("ca",mCaFilename); + } + + if(mUseLzo) { + cfg+="comp-lzo\n"; + } + + if(mUseTLSAuth) { + if(mAuthenticationType==TYPE_STATICKEYS) + cfg+=insertFileData("secret",mTLSAuthFilename); + else + cfg+=insertFileData("tls-auth",mTLSAuthFilename); + + if(nonNull(mTLSAuthDirection)) { + cfg+= "key-direction "; + cfg+= mTLSAuthDirection; + cfg+="\n"; + } + + } + + if(!mUsePull ) { + if(nonNull(mIPv4Address)) + cfg +="ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n"; + + if(nonNull(mIPv6Address)) + cfg +="ifconfig-ipv6 " + mIPv6Address + "\n"; + } + + if(mUsePull && mRoutenopull) + cfg += "route-nopull\n"; + + String routes = ""; + int numroutes=0; + if(mUseDefaultRoute) + routes += "route 0.0.0.0 0.0.0.0\n"; + else + for(String route:getCustomRoutes()) { + routes += "route " + route + "\n"; + numroutes++; + } + + + if(mUseDefaultRoutev6) + cfg += "route-ipv6 ::/0\n"; + else + for(String route:getCustomRoutesv6()) { + routes += "route-ipv6 " + route + "\n"; + numroutes++; + } + + // Round number to next 100 + if(numroutes> 90) { + numroutes = ((numroutes / 100)+1) * 100; + cfg+="# Alot of routes are set, increase max-routes\n"; + cfg+="max-routes " + numroutes + "\n"; + } + cfg+=routes; + + if(mOverrideDNS || !mUsePull) { + if(nonNull(mDNS1)) + cfg+="dhcp-option DNS " + mDNS1 + "\n"; + if(nonNull(mDNS2)) + cfg+="dhcp-option DNS " + mDNS2 + "\n"; + if(nonNull(mSearchDomain)) + cfg+="dhcp-option DOMAIN " + mSearchDomain + "\n"; + + } + + if(mNobind) + cfg+="nobind\n"; + + + + // Authentication + if(mCheckRemoteCN) { + if(mRemoteCN == null || mRemoteCN.equals("") ) + cfg+="tls-remote " + mServerName + "\n"; + else + cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n"; + } + if(mExpectTLSCert) + cfg += "remote-cert-tls server\n"; + + + if(nonNull(mCipher)){ + cfg += "cipher " + mCipher + "\n"; + } + + + // Obscure Settings dialog + if(mUseRandomHostname) + cfg += "#my favorite options :)\nremote-random-hostname\n"; + + if(mUseFloat) + cfg+= "float\n"; + + if(mPersistTun) { + cfg+= "persist-tun\n"; + cfg+= "# persist-tun also sets persist-remote-ip to avoid DNS resolve problem\n"; + cfg+= "persist-remote-ip\n"; + } + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true); + if(usesystemproxy) { + cfg+= "# Use system proxy setting\n"; + cfg+= "management-query-proxy\n"; + } + + + if(mUseCustomConfig) { + cfg += "# Custom configuration options\n"; + cfg += "# You are on your on own here :)\n"; + cfg += mCustomConfigOptions; + cfg += "\n"; + + } + + + + return cfg; + } + + //! Put inline data inline and other data as normal escaped filename + private String insertFileData(String cfgentry, String filedata) { + if(filedata==null) { + return String.format("%s %s\n",cfgentry,"missing"); + }else if(filedata.startsWith(VpnProfile.INLINE_TAG)){ + String datawoheader = filedata.substring(VpnProfile.INLINE_TAG.length()); + return String.format("<%s>\n%s\n\n",cfgentry,datawoheader,cfgentry); + } else { + return String.format("%s %s\n",cfgentry,openVpnEscape(filedata)); + } + } + + private boolean nonNull(String val) { + if(val == null || val.equals("")) + return false; + else + return true; + } + + private Collection getCustomRoutes() { + Vector cidrRoutes=new Vector(); + if(mCustomRoutes==null) { + // No routes set, return empty vector + return cidrRoutes; + } + for(String route:mCustomRoutes.split("[\n \t]")) { + if(!route.equals("")) { + String cidrroute = cidrToIPAndNetmask(route); + if(cidrRoutes == null) + return null; + + cidrRoutes.add(cidrroute); + } + } + + return cidrRoutes; + } + + private Collection getCustomRoutesv6() { + Vector cidrRoutes=new Vector(); + if(mCustomRoutesv6==null) { + // No routes set, return empty vector + return cidrRoutes; + } + for(String route:mCustomRoutesv6.split("[\n \t]")) { + if(!route.equals("")) { + cidrRoutes.add(route); + } + } + + return cidrRoutes; + } + + + + private String cidrToIPAndNetmask(String route) { + String[] parts = route.split("/"); + + // No /xx, assume /32 as netmask + if (parts.length ==1) + parts = (route + "/32").split("/"); + + if (parts.length!=2) + return null; + int len; + try { + len = Integer.parseInt(parts[1]); + } catch(NumberFormatException ne) { + return null; + } + if (len <0 || len >32) + return null; + + + long nm = 0xffffffffl; + nm = (nm << (32-len)) & 0xffffffffl; + + String netmask =String.format("%d.%d.%d.%d", (nm & 0xff000000) >> 24,(nm & 0xff0000) >> 16, (nm & 0xff00) >> 8 ,nm & 0xff ); + return parts[0] + " " + netmask; + } + + + + private String[] buildOpenvpnArgv(File cacheDir) + { + Vector args = new Vector(); + + // Add fixed paramenters + //args.add("/data/data/se.leap.openvpn/lib/openvpn"); + args.add(cacheDir.getAbsolutePath() +"/" + VpnProfile.MINIVPN); + + args.add("--config"); + args.add(cacheDir.getAbsolutePath() + "/" + OVPNCONFIGFILE); + // Silences script security warning + + args.add("script-security"); + args.add("0"); + + + return (String[]) args.toArray(new String[args.size()]); + } + + public Intent prepareIntent(Context context) { + String prefix = context.getPackageName(); + + Intent intent = new Intent(context,OpenVpnService.class); + + if(mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { + /*if(!saveCertificates(context)) + return null;*/ + } + + intent.putExtra(prefix + ".ARGV" , buildOpenvpnArgv(context.getCacheDir())); + intent.putExtra(prefix + ".profileUUID", mUuid.toString()); + + ApplicationInfo info = context.getApplicationInfo(); + intent.putExtra(prefix +".nativelib",info.nativeLibraryDir); + + try { + FileWriter cfg = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE); + cfg.write(getConfigFile(context)); + cfg.flush(); + cfg.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + return intent; + } + + private boolean saveCertificates(Context context) { + PrivateKey privateKey = null; + X509Certificate[] cachain=null; + try { + privateKey = KeyChain.getPrivateKey(context,mAlias); + mPrivateKey = privateKey; + + cachain = KeyChain.getCertificateChain(context, mAlias); + if(cachain.length <= 1 && !nonNull(mCaFilename)) + OpenVPN.logMessage(0, "", context.getString(R.string.keychain_nocacert)); + + for(X509Certificate cert:cachain) { + OpenVPN.logInfo(R.string.cert_from_keystore,cert.getSubjectDN()); + } + + + + + if(nonNull(mCaFilename)) { + try { + Certificate cacert = getCacertFromFile(); + X509Certificate[] newcachain = new X509Certificate[cachain.length+1]; + for(int i=0;i= 1){ + X509Certificate usercert = cachain[0]; + + FileWriter userout = new FileWriter(context.getCacheDir().getAbsolutePath() + "/" + VpnProfile.OVPNCONFIGUSERCERT); + + PemWriter upw = new PemWriter(userout); + upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded())); + upw.close(); + + } + + return true; + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (CertificateException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (KeyChainException e) { + OpenVPN.logMessage(0,"",context.getString(R.string.keychain_access)); + } + return false; + } + private Certificate getCacertFromFile() throws FileNotFoundException, CertificateException { + CertificateFactory certFact = CertificateFactory.getInstance("X.509"); + + InputStream inStream; + + if(mCaFilename.startsWith(INLINE_TAG)) + inStream = new ByteArrayInputStream(mCaFilename.replace(INLINE_TAG,"").getBytes()); + else + inStream = new FileInputStream(mCaFilename); + + return certFact.generateCertificate(inStream); + } + + + //! Return an error if somethign is wrong + public int checkProfile(Context context) { +/* if(mAuthenticationType==TYPE_KEYSTORE || mAuthenticationType==TYPE_USERPASS_KEYSTORE) { + if(mAlias==null) + return R.string.no_keystore_cert_selected; + }*/ + + if(!mUsePull) { + if(mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null) + return R.string.ipv4_format_error; + } + if(isUserPWAuth() && !nonNull(mUsername)) { + return R.string.error_empty_username; + } + if(!mUseDefaultRoute && getCustomRoutes()==null) + return R.string.custom_route_format_error; + + // Everything okay + return R.string.no_error_found; + + } + + //! Openvpn asks for a "Private Key", this should be pkcs12 key + // + public String getPasswordPrivateKey() { + if(mTransientPCKS12PW!=null) { + String pwcopy = mTransientPCKS12PW; + mTransientPCKS12PW=null; + return pwcopy; + } + switch (mAuthenticationType) { + case TYPE_PKCS12: + case TYPE_USERPASS_PKCS12: + return mPKCS12Password; + + case TYPE_CERTIFICATES: + case TYPE_USERPASS_CERTIFICATES: + return mKeyPassword; + + case TYPE_USERPASS: + case TYPE_STATICKEYS: + default: + return null; + } + } + private boolean isUserPWAuth() { + switch(mAuthenticationType) { + case TYPE_USERPASS: + case TYPE_USERPASS_CERTIFICATES: + case TYPE_USERPASS_KEYSTORE: + case TYPE_USERPASS_PKCS12: + return true; + default: + return false; + + } + } + + + public boolean requireTLSKeyPassword() { + if(!nonNull(mClientKeyFilename)) + return false; + + String data = ""; + if(mClientKeyFilename.startsWith(INLINE_TAG)) + data = mClientKeyFilename; + else { + char[] buf = new char[2048]; + FileReader fr; + try { + fr = new FileReader(mClientKeyFilename); + int len = fr.read(buf); + while(len > 0 ) { + data += new String(buf,0,len); + len = fr.read(buf); + } + fr.close(); + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + return false; + } + + } + + if(data.contains("Proc-Type: 4,ENCRYPTED")) + return true; + else if(data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----")) + return true; + else + return false; + } + + public int needUserPWInput() { + if((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12)&& + (mPKCS12Password == null || mPKCS12Password.equals(""))) { + if(mTransientPCKS12PW==null) + return R.string.pkcs12_file_encryption_key; + } + + if(mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) { + if(requireTLSKeyPassword() && !nonNull(mKeyPassword)) + if(mTransientPCKS12PW==null) { + return R.string.private_key_password; + } + } + + if(isUserPWAuth() && (mPassword.equals("") || mPassword == null)) { + if(mTransientPW==null) + return R.string.password; + + } + return 0; + } + + public String getPasswordAuth() { + if(mTransientPW!=null) { + String pwcopy = mTransientPW; + mTransientPW=null; + return pwcopy; + } else { + return mPassword; + } + } + + + // Used by the Array Adapter + @Override + public String toString() { + return mName; + } + + + public String getUUIDString() { + return mUuid.toString(); + } + + + public PrivateKey getKeystoreKey() { + return mPrivateKey; + } + + + +} + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_menu_add.png b/app/src/main/res/drawable-hdpi/ic_menu_add.png new file mode 100644 index 00000000..444e8a5e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_add.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_login.png b/app/src/main/res/drawable-hdpi/ic_menu_login.png new file mode 100644 index 00000000..afa152b2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_login.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png b/app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png new file mode 100644 index 00000000..577e0558 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_settings_holo_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_trash_holo_light.png b/app/src/main/res/drawable-hdpi/ic_menu_trash_holo_light.png new file mode 100644 index 00000000..c62295aa Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_trash_holo_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_sysbar_quicksettings.png b/app/src/main/res/drawable-hdpi/ic_sysbar_quicksettings.png new file mode 100644 index 00000000..47b4ba23 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_sysbar_quicksettings.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png b/app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png new file mode 100644 index 00000000..dfb962b9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_vpn_disconnected.png differ diff --git a/app/src/main/res/drawable-hdpi/icon.png b/app/src/main/res/drawable-hdpi/icon.png new file mode 100644 index 00000000..02ede650 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/icon.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_menu_add.png b/app/src/main/res/drawable-ldpi/ic_menu_add.png new file mode 100644 index 00000000..89620af8 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_menu_add.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_menu_login.png b/app/src/main/res/drawable-ldpi/ic_menu_login.png new file mode 100644 index 00000000..d4181de5 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_menu_login.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_stat_vpn.png b/app/src/main/res/drawable-ldpi/ic_stat_vpn.png new file mode 100644 index 00000000..f973015c Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_stat_vpn.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_vpn_disconnected.png b/app/src/main/res/drawable-ldpi/ic_vpn_disconnected.png new file mode 100644 index 00000000..22f3497e Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_vpn_disconnected.png differ diff --git a/app/src/main/res/drawable-ldpi/icon.png b/app/src/main/res/drawable-ldpi/icon.png new file mode 100644 index 00000000..e312075d Binary files /dev/null and b/app/src/main/res/drawable-ldpi/icon.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_add.png b/app/src/main/res/drawable-mdpi/ic_menu_add.png new file mode 100644 index 00000000..361c7c46 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_menu_add.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_settings_holo_light.png b/app/src/main/res/drawable-mdpi/ic_menu_settings_holo_light.png new file mode 100644 index 00000000..f32a37e4 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_menu_settings_holo_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_trash_holo_light.png b/app/src/main/res/drawable-mdpi/ic_menu_trash_holo_light.png new file mode 100644 index 00000000..08291855 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_menu_trash_holo_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_sysbar_quicksettings.png b/app/src/main/res/drawable-mdpi/ic_sysbar_quicksettings.png new file mode 100644 index 00000000..79281042 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_sysbar_quicksettings.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_vpn_disconnected.png b/app/src/main/res/drawable-mdpi/ic_vpn_disconnected.png new file mode 100644 index 00000000..f8b02bfb Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_vpn_disconnected.png differ diff --git a/app/src/main/res/drawable-mdpi/icon.png b/app/src/main/res/drawable-mdpi/icon.png new file mode 100644 index 00000000..468314c6 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/icon.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_login.png b/app/src/main/res/drawable-xhdpi/ic_menu_login.png new file mode 100644 index 00000000..5095ed97 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_menu_login.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_settings_holo_light.png b/app/src/main/res/drawable-xhdpi/ic_menu_settings_holo_light.png new file mode 100644 index 00000000..aa33c388 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_menu_settings_holo_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_trash_holo_light.png b/app/src/main/res/drawable-xhdpi/ic_menu_trash_holo_light.png new file mode 100644 index 00000000..bd3fd784 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_menu_trash_holo_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_sysbar_quicksettings.png b/app/src/main/res/drawable-xhdpi/ic_sysbar_quicksettings.png new file mode 100644 index 00000000..a057db8b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_sysbar_quicksettings.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_vpn_disconnected.png b/app/src/main/res/drawable-xhdpi/ic_vpn_disconnected.png new file mode 100644 index 00000000..7f44c46f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_vpn_disconnected.png differ diff --git a/app/src/main/res/drawable-xhdpi/icon.png b/app/src/main/res/drawable-xhdpi/icon.png new file mode 100644 index 00000000..ead03720 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon.png differ diff --git a/app/src/main/res/layout-xlarge/about.xml b/app/src/main/res/layout-xlarge/about.xml new file mode 100644 index 00000000..6ab88737 --- /dev/null +++ b/app/src/main/res/layout-xlarge/about.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-xlarge/client_dashboard.xml b/app/src/main/res/layout-xlarge/client_dashboard.xml new file mode 100644 index 00000000..bd644e1e --- /dev/null +++ b/app/src/main/res/layout-xlarge/client_dashboard.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-xlarge/configuration_wizard_activity.xml b/app/src/main/res/layout-xlarge/configuration_wizard_activity.xml new file mode 100644 index 00000000..bb169e00 --- /dev/null +++ b/app/src/main/res/layout-xlarge/configuration_wizard_activity.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/eip_service_fragment.xml b/app/src/main/res/layout-xlarge/eip_service_fragment.xml new file mode 100644 index 00000000..e5c7f23d --- /dev/null +++ b/app/src/main/res/layout-xlarge/eip_service_fragment.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-xlarge/log_in_dialog.xml b/app/src/main/res/layout-xlarge/log_in_dialog.xml new file mode 100644 index 00000000..3a9eebb8 --- /dev/null +++ b/app/src/main/res/layout-xlarge/log_in_dialog.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/logwindow.xml b/app/src/main/res/layout-xlarge/logwindow.xml new file mode 100644 index 00000000..4051c92c --- /dev/null +++ b/app/src/main/res/layout-xlarge/logwindow.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/provider_detail_fragment.xml b/app/src/main/res/layout-xlarge/provider_detail_fragment.xml new file mode 100644 index 00000000..4abbaa17 --- /dev/null +++ b/app/src/main/res/layout-xlarge/provider_detail_fragment.xml @@ -0,0 +1,43 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/provider_list_fragment.xml b/app/src/main/res/layout-xlarge/provider_list_fragment.xml new file mode 100644 index 00000000..59dd37d1 --- /dev/null +++ b/app/src/main/res/layout-xlarge/provider_list_fragment.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/layout-xlarge/provider_list_item.xml b/app/src/main/res/layout-xlarge/provider_list_item.xml new file mode 100644 index 00000000..ec5db117 --- /dev/null +++ b/app/src/main/res/layout-xlarge/provider_list_item.xml @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml new file mode 100644 index 00000000..4b3f16e0 --- /dev/null +++ b/app/src/main/res/layout/about.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/client_dashboard.xml b/app/src/main/res/layout/client_dashboard.xml new file mode 100644 index 00000000..a5387efd --- /dev/null +++ b/app/src/main/res/layout/client_dashboard.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/configuration_wizard_activity.xml b/app/src/main/res/layout/configuration_wizard_activity.xml new file mode 100644 index 00000000..f3d0e48b --- /dev/null +++ b/app/src/main/res/layout/configuration_wizard_activity.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/eip_service_fragment.xml b/app/src/main/res/layout/eip_service_fragment.xml new file mode 100644 index 00000000..5992a873 --- /dev/null +++ b/app/src/main/res/layout/eip_service_fragment.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/log_in_dialog.xml b/app/src/main/res/layout/log_in_dialog.xml new file mode 100644 index 00000000..c8a2f0a8 --- /dev/null +++ b/app/src/main/res/layout/log_in_dialog.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/logwindow.xml b/app/src/main/res/layout/logwindow.xml new file mode 100644 index 00000000..4051c92c --- /dev/null +++ b/app/src/main/res/layout/logwindow.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/provider_detail_fragment.xml b/app/src/main/res/layout/provider_detail_fragment.xml new file mode 100644 index 00000000..eb90fad9 --- /dev/null +++ b/app/src/main/res/layout/provider_detail_fragment.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/provider_list_fragment.xml b/app/src/main/res/layout/provider_list_fragment.xml new file mode 100644 index 00000000..70dbae0d --- /dev/null +++ b/app/src/main/res/layout/provider_list_fragment.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout/provider_list_item.xml b/app/src/main/res/layout/provider_list_item.xml new file mode 100644 index 00000000..8746f6f8 --- /dev/null +++ b/app/src/main/res/layout/provider_list_item.xml @@ -0,0 +1,43 @@ + + + + + + + + + + diff --git a/app/src/main/res/menu/client_dashboard.xml b/app/src/main/res/menu/client_dashboard.xml new file mode 100644 index 00000000..676c07c7 --- /dev/null +++ b/app/src/main/res/menu/client_dashboard.xml @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/app/src/main/res/menu/configuration_wizard_activity.xml b/app/src/main/res/menu/configuration_wizard_activity.xml new file mode 100644 index 00000000..9936b6dc --- /dev/null +++ b/app/src/main/res/menu/configuration_wizard_activity.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/logmenu.xml b/app/src/main/res/menu/logmenu.xml new file mode 100644 index 00000000..c4087585 --- /dev/null +++ b/app/src/main/res/menu/logmenu.xml @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/app/src/main/res/values-ca/arrays.xml b/app/src/main/res/values-ca/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-ca/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml new file mode 100644 index 00000000..2b250da1 --- /dev/null +++ b/app/src/main/res/values-ca/strings.xml @@ -0,0 +1,41 @@ + + + Cancel·la + Quan a + No s\'ha trobat cap error + S\'ha trobat un error a la configuració + Conecta a la VPN + No s\'ha trobat el perfil especificat a l\'accès directe + La ruta ha estat refusas per Android + Desconecta + Neteja el registre + Cancela la confirmació + Desconcta la conexió VPN/cancela l\'intent de conexió? + Edita la configuració VPN + "Error: " + Neteja + info + Mostra els detalls de conexió + Servidor DNS: %s + Domini DNS: %s + Rutes: %s + Rutes IPv6: %s + Envia el fitxer de registre + Envia + Fitxer de registre de LEAP Android + S\'ha copiat l\'entrada al porta-retalls + Esperant el missatge d\'estat... + Perfil importat + Perfil importat %d + Contrasenya de la clau privada + Contrasenya + Construint la configuració... + Estat de la xarxa %s + Traducció al catala per Sergi Almacellas +<sergi@koolpi.com> + Utilitzant el proxy %1$s %2$d + Ignorar + Reinicia + Els canvis de configuració s\'apliquen desprès de reinicar la VPN. (Re)inicar la VPN ara? + S\'ha canviat la configuració + diff --git a/app/src/main/res/values-cs/arrays.xml b/app/src/main/res/values-cs/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-cs/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml new file mode 100644 index 00000000..68c5aeaf --- /dev/null +++ b/app/src/main/res/values-cs/strings.xml @@ -0,0 +1,79 @@ + + + Storno + Zdrojové kódy a seznam problémů je na https://github.com/leapcode/bitmask_android/ + Tento program používá následující komponenty; viz zdrojový kód pro detaily o licenci + O programu + Bez chyb + Chyba v konfiguraci + Chyba při zpracování IPv4 adresy + Chyba při zpracování vlastního směrování + Profil zvolený ve zkratce nenalezen + Směrování odmítnuto Androidem + Odpojit + vymazat log + Zrušit potvrzení + Odpojit/Zrušit připojování? + Změnit nastavení VPN + Na některých ICS systémech může být oprávnění pro /dev/tun špatně nastavené, nebo tun modul může zcela chybět. + Chyba při otvírání tun zařízení + "Chyba: " + Vymazat + info + Zobrazit detaily spojení + Poslední nastavení rozhraní pro OpenVPN: + Místní IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS server: %s + DNS doména: %s + Směrování: %s + Směrování IPv6: %s + Získány informace o rozhraní %1$s a %2$s, předpokládám, že druhá adresa je adresa vzdáleného kolegy. Používám /32 masku pro místní IP adresu. Mód OpenVPN je \"%3$s\". + %1$s a %2$s jako IP adresy s CIDR maskou nedávají smysl, používám /32 jako masku. + Směrování opraveno z %1$s/%2$s na %3$s/%2$s + Nelze přistoupit k Androidímu úložišti certifikátů. To může být způsobeno aktualizací firmwaru nebo obnovováním aplikace a jejího nastavení ze zálohy. Uprav VPN profil a znovu vyber certifikát pro vytvoření patřičných povolení. + %1$s %2$s + Odeslat soubor s logem + Odeslat + LEAP Android logovací soubor + Záznam z logu zkopírován do schránky + Tvůj obrázek není podporovaný rozhraním VPNService, je mi líto :-( + Odmítám otevřít tun zařízení bez informace o IP + Čekám na zprávu o stavu… + importovaný profil + importovaný profil %d + Jméno nesmí být prázdné. + Šifrovací klíč PKCS12 + Heslo k soukromému klíči + Heslo + Vytvářím konfiguraci… + Získán certifikát \'%s\' z úložiště + Stav sítě: %s + Žádný CA certifikát nebyl získán z úložiště, autentikace pravděpodobně selže. + Spuštěno na %1$s (%2$s) %3$s, Android API %4$d + Chyba při podepisování klíčem %1$s: %2$s + Českou lokalizaci zpracoval Jan Baier <baier.jan@gmail.com> + Není použit DNS server. Překlad jmen nemusí fungovat. Zvaž nastavení vlastního DNS serveru. + Nemohu přidat DNS server \"%1$s\", odmítnuto systémem: %2$s + Chyba při zjišťování nastavení proxy: %s + Používám proxy %1$s %2$d + Ignorovat + Restartovat + Změna nastavení začne platit až po restartu VPN. Restartovat teď? + Nastavení změněno + OpenVPN neočekávaně havarovalo. Zvaž možnost použití volby poslat Minidump z hlavního menu + Bitmask - %s + Připojuji se + Čekání na odpověď serveru + Ověřuji autorizaci + Stahuji konfiguraci klienta + Nastavuji IP adresu + Přidávám trasy + Připojeno + Obnovuji připojení + Ukončuji + Neběží + Překlad názvů + Připojuji (TCP) + Přihlášení nebylo úspěšné + Čekání na použitelnou síť + diff --git a/app/src/main/res/values-de/arrays.xml b/app/src/main/res/values-de/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-de/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 00000000..84d93cfe --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,80 @@ + + + Abbrechen + Quellcode und Issue Tracker sind verfügbar unter https://github.com/leapcode/bitmask_android/ + Dieses Programm nutzt die folgenden Komponenten. Die kompletten Lizenzdetails sind im Quelltext verfügbar. + Über + Kein Fehler. + Fehler in der Konfiguration + Kann die die IPv4 Adresse nicht parsen + Kann die manuell angegeben Routen nicht parsen + Von der Verknüpfung referenziertes Profil konnte nicht gefunden werden + Route von Android zurückgewiesen. + Trennen + Log löschen. + Trennungsbestätigung + Möchten Sie das VPN trennen bzw. den Verbindungsversuch abbrechen? + Ändere VPN Einstellungen + Auf manchen ROM Version sind eventuell die Zugriffsrechte von /dev/tun falsch oder das tun Kernel Modul fehlt. Für Cyanogenmod 9 ROMs mit root gibt einen provisorischen Fix in den generellen Einstellungen. + Das Öffnen des tun Interfaces ist katastrophal gescheitert + "Fehler: " + Clear + Info + Zeige Verbindungsdetails + Letzte Interface Konfiguration von OpenVPN: + Lokale IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS Server: %s + DNS Domäne: %s + Routen IPv4: %s + Routen IPv6: %s + Interface Information %1$s und %2$s, nehme an, die zweite Adresse ist die Peer Adresse. Benutze /32 Netzmaske für die lokale IP Adresse. Interface Modus spezifiziert von OpenVPN ist \"%3$s\". + Die Route %1$s mit der Netzmaske %2$s ist keine Route mit einer CIDR Netzmaske, benutze /32 als Netzmaske. + Route %1$s/%2$s korrigiert zu %3$s/%2$s + Kann nicht auf die Android Keychain Zertifikate zugreifen (dies kann durch ein System Update oder durch Zurücksichern der Anwendung aus einem Backup hervorgerufen werden). Bitte editieren Sie das VPN und wählen Sie erneut das Zertifikat in dem Grundeinstellungen aus, um die Zugriffsberechtigung für das Zertifikat wieder herzustellen. + %1$s %2$s + Sende Logdatei + Sende + LEAP Android log Datei + Log Eintrag in die Zwischenablage kopiert + Dieses Android ROM enthält keine VPNService API. Sorry :( + Verweigere tun Gerät zu öffnen ohne IP Information + Warte auf OpenVPN Status Nachricht… + Importiertes Profil + Importiertes Profil %d + Der Benutzername darf nicht leer sein + PKCS12 Veschlüsslungspassword + Passphrase privater Schlüssel + Passwort + Generiere OpenVPN Konfiguration… + Zertifikat (KeyStore): \'%s\' + Netzwerkstatus: %s + Beim Abfragen des Android KeyStore wurde kein CA Zertifikat zurückgegeben. Überprüfen des Serverzertifikat wird wahrscheinlich fehlschlagen. Geben Sie manuell ein CA Zertifikat an. + Modell %1$s (%2$s) %3$s, Android API %4$d + Fehler beim Zugriff auf den Android Keystore %1$s: %2$s + Deutsche Übersetzung von Arne Schwabe <arne@rfc2549.org> + Es werden keine DNS Server gesetzt. Möglicherweise wird die DNS Auflösung nicht funktionieren. Ziehen Sie in Betracht die Option \"eigene DNS Server\" zu benutzen. + Konnte den DNS Server \"%1$s\" nicht hinzufügen, da das System ihn zurückweist mit %2$s + Fehler beim Ermitteln der Proxy Einstellungen: %s + Benutzt Proxy %1$s %2$d + Ignorieren + Neu verbinden + Konfigurationsänderungen werden erst nach einem VPN Neustart aktiv. Jetzt neu verbinden? + Konfiguration geändert + Der OpenVPN Prozess ist unerwartet abgestürzt. Bitte erwägen Sie die Option \"Minidump senden\" im Hauptmenü + Bitmask - %s + + Verbinde + Warte auf Serverantwort + Authentifiziere + Warte auf Clientkonfiguration + Weise IP Adressen zu + Hinzufügen von Routen + Verbunden + Wiederverbinden + Beende + Kein OpenVPN Prozess + Löse Hostnamen auf + Verbinde (TCP) + Authentifizierung fehlgeschlagen + Warte auf Internetverbindung + diff --git a/app/src/main/res/values-es/arrays.xml b/app/src/main/res/values-es/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-es/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml new file mode 100644 index 00000000..f8046a3f --- /dev/null +++ b/app/src/main/res/values-es/strings.xml @@ -0,0 +1,60 @@ + + + Cancelar + Codigo fuente y sistema de reporte de errores disponibles en https://github.com/leapcode/bitmask_android/ + El programa utiliza los siguientes componentes. Vea los códigos fuentes para obtener más información sobre las licencias + Acerca de + Ruta rechazada por Android + Desconectar + Limpiar registro + Cancelar confirmación + ¿Desconectar la conexión VPN/cancelar el intento de conexión? + Modificar la configuración de VPN + En algunas imágenes personalizadas de ICS los permisos sobre /dev/tun podrían ser incorrectos, o el módulo tun podría faltar completamente. + La apertura de la interfaz tun falĺó + "Error: " + Borrar + información + Mostrar detalles de la conexión + Última configuración de interfaz de OpenVPN: + Local IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + Servidor DNS: %s + Dominio DNS: %s + Rutas: %s + Rutas IPv6: %s + Información de la interfaz obtenida %1$s and %2$s, asumiendo que la segunda dirección es una dirección equivalente del remoto. Usando una máscara de red /32 para la IP local. El modo dado por OpenVPN es \"%3$s\". + No tienen sentido %1$s y %2$s como ruta IP con máscara de red CIDR, usando /32 como máscara de red. + Ruta conectada de %1$s/%2$s a %3$s/%2$s + No se puede acceder a los certificados de Android. Puede ser causado por una actualización de firmware o por restaurar una copia de seguridad de la aplicación/configuración de la aplicación. Por favor edite la VPN y vuelva a seleccionar el certificado bajo meltdownajustes básicos para recrear los permisos para acceder al certificado. + %1$s %2$s + Enviar el archivo de registro + Enviar + Archivo de registro de LEAP de Android + Entrada de registro copiada al Portapapeles + Su imagen no es compatible con la API de VPNService, lo siento :( + Negandose a abrir el dispositivo tun sin información de IP + Esperando el mensaje de estado... + perfil importado + perfil importado %d + El nombre de usuario no debe estar vacío. + Clave PKCS12 de cifrado de archivos + Contraseña de clave privada + Contraseña + Construyendo configuracion... + Conseguido el certificado de \'%s\' de almacén de claves + Estado de la red: %s + No se obtuvo ningún certificado de CA al leer el almacén de claves de Android. La autenticación probablemente fallará. + Ejecutándose en %1$s (%2$s) %3$s, API de Android %4$d + Error al firmar con la llave del almacén de llaves de Android %1$s: %2$s + Traducción al español por José Luis Bandala Perez<luis.449bp@gmail.com> + Sin servidores DNS utilizados. La resolución de nombres puede que no funcione. Considere configurar servidores DNS personalizados + No se puede agregar el servidor DNS \"%1$s\", rechazado por el sistema: %2$s + Error al obtener la configuración de proxy: %s + Usando proxy %1$s %2$d + Ignorar + Reiniciar + Los cambios de configuración se aplican después de reiniciar la VPN. ¿(Re)iniciar la VPN ahora? + Configuración cambiada + + OpenVPN falló inesperadamente. Por favor considere usar la opción envío de minivolcado en el menú principal + diff --git a/app/src/main/res/values-et/arrays.xml b/app/src/main/res/values-et/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-et/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml new file mode 100644 index 00000000..04dc9572 --- /dev/null +++ b/app/src/main/res/values-et/strings.xml @@ -0,0 +1,66 @@ + + + Tühista + Lähtetekst ja probleemihaldur asuvad veebilehel https://github.com/leapcode/bitmask_android/ + Programmis kasutatakse järgnevaid komponente. Detailse litsenseerimisinfo leiate lähtekoodist + Lähemalt + Vigu ei leitud + Konfiguratsiooni viga + Sisestatud IPv4 aadress ei allu süntaksianalüüsile + Kohandatud marsruudid ei allu süntaksianalüüsile + Ühendu VPN\'iga + Lühivalikus määratud profiil puudub + Androidi poolt keelatud ruutingud + Katkesta ühendus + Tühjenda logi + Loobu kinnitusest + Katkesta VPN ühendus/tühista ühendumise katse? + Muuda VPN seadistusi + Mõnel modifitseeritud ICS versioonil võivad /dev/tun õigused olla valed, või selle moodul sootuks puududa. + Tun liidese avamine ebaõnnestus + "Viga:" + Tühjenda + info + Näita ühenduse andmeid + Viimane liidese konfigureerimine OpenVPN poolt: + Lokaalne IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS Server: %s + DNS domeen: %s + Marsruudid: %s + IPv6 marsruudid: %s + Liidese andmed on %1$s ja %2$s, eeldades et teine aadress on eemalasuva serveri aadress. Lokaalse IP jaoks kasutatakse /32 võrgumaski. OpenVPN teatab režiimiks %3$s\". + %1$s ja %2$s on mõttetud CIDR võrgumaskiga IP marsruutidest, võrgumaskiks määratakse /32. + %1$s/%2$s marsruut parandatud: %3$s/%2$s + Androidi Keychain sertifikaadid on kättesaamatud. See võib olla põhjustatud püsivara uuendamisest või appide/apiseadistuste taastamisest. Sertifikaatide pääsuõiguste taastamiseks redigeerige palun VPN seadistusi ja valige uuesti üldiste seadistuste alt sertifikaat. + %1$s %2$s + Saada logifail + Saada + LEAP Androidile logifail + Logikirje kopeeriti lõikepuhvrisse + Teie süsteemitarkvara ei toeta VPNService API\'t, vabandame :( + IP andmeteta keeldutakse tun liidese avamisest + Ootan olekuteadet… + imporditud profiil + imporditud profiil %d + Kasutajanimi peab olema määratud. + PKCS12 faili krüpteerimisvõti + Privaatse võtme salasõna + Salasõna + Koostatakse konfiguratsiooni… + Saadud sertifikaat \'%s\' võtmehoidlast + Võrgu olek: %s + Androidi võtmehoidlast lugemine ei andnud ühtegi CA sertifikaati. Suure tõenäosusega autentimine ebaõnnestub. + Töötamas %1$s (%2$s) %3$s peal, Android API %4$d + Viga allkirjastamisel Androidi võtmehoidla võtmega %1$s: %2$s + Eesti keelde tõlkis Robert Tiismus + Ühtegi DNS serverit ei kasutata. Nimelahendus ei pruugi töötada. Vae kohandatud DNS serveri kasutust + DNS serveri \"%1$s\" lisamine ebaõnnestus, süsteemi poolt keelduti: %2$s + Viga proxy seadistuste vastuvõtul: %s + Kasutusel proxy %1$s %2$d + Ignoreeri + Uuestilaadimine + Konfiguratsioonimuudatused rakendatakse peale VPN uuestilaadimist. Kas soovite VPN kohe (uuesti)laadida? + Konfiguratsiooni muudeti + + OpenVPN jooksis ootamatult kokku. Palun kaaluge \"saada Minitõmmis\" valiku lubamist peamenüüs + diff --git a/app/src/main/res/values-fr/arrays.xml b/app/src/main/res/values-fr/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-fr/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml new file mode 100644 index 00000000..79e4d70c --- /dev/null +++ b/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,65 @@ + + + "Annuler" + "Le code source et le tracker de bugs est disponible ici: https://github.com/leapcode/bitmask_android/ " + "Le programme utilise les composants suivants. Voir le code source pour plus de détails sur les licences." + "À propos" + "Aucune erreur" + "Erreur dans la configuration" + "Impossible d\'analyser l\'adresse IPv4" + "Impossible d\'analyser les règles de redirection personnalisés" + "Se connecter au VPN" + "Profil spécifié dans raccourci introuvable" + "Route rejetée par Android" + "Déconnecter" + "Effacer les logs" + "Annuler la confirmation" + "Déconnecter le VPN connecté / annuler la tentative de connexion ?" + "Modifier les paramètres VPN" + "Sur certaines ROMs ICS les permissions de /dev/tun peuvent être incorrectes, ou le module Tun peut être manquant." + "L\'ouverture de l\'interface Tun a échoué." + "Erreur: " + "Effacer" + "infos" + "Afficher les détails de la connexion" + "Dernière configuration connue de l\'interface d\'OpenVPN:" + "IPv4 locale: %1$s/%2$d IPv6: %3$s MTU: %4$d" + "Serveur DNS: %s" + "Domaine DNS: %s" + "Routes: %s" + "Routes IPv6: %s" + "Informations récupérées de l\'interface: %1$s et %2$s , en supposant que la seconde adresse est l\'adresse peer du réseau distant. Utilisation du masque de réseau /32 pour l\'IP locale. Mode donné par OpenVPN: \"%3$s\"." + "Ne peut pas donner un sens à %1$s et %2$s comme routage IP avec masque réseau de type CIDR, en utilisant /32 comme masque de réseau." + "Règle de redirection corrigée: %1$s / %2$s en %3$s / %2$s" + \"Impossible d\'accéder aux certificats \"Android Keychain\". (Peut être causé par une mise à jour du firmware ou par une restauration d\'une sauvegarde des paramètres de l\'application). Veuillez modifier le profil VPN et sélectionnez de nouveau le certificat dans les réglages de base pour recréer l\'autorisation d\'accéder au certificat.\". + "%1$s %2$s" + "Envoyer le fichier de log" + "Envoyer" + "Fichier de log LEAP Android" + "Entrée du log copiée" + "Votre ROM ne prend pas en charge l\'API VPNService, désolé :(" + "Impossible d\'ouvrir le périphérique TUN sans informations IP" + "Attente du status..." + "profil importé" + "profil importé: %d" + "Le nom d\'utilisateur ne doit pas être vide." + "Fichier de clé de cryptage PKCS12" + "Mot de passe de clé privée" + "Mot de passe" + "Création de la configuration ..." + "Certificat \'%s\' obtenu" + "État du réseau: %s" + "Aucun certificat CA renvoyée lors de la lecture depuis le gestionnaire de clés. L\'authentification échouera probablement." + "Fonctionnant sur %1$s (%2$s) %3$s , Android API %4$d" + "Erreur de signature de la clé %1$s : %2$s par le gestionnaire d\'Android" + French translation by Stanislas Bach<stanislasbach@gmail.com> + "Pas de serveurs DNS utilisés. La résolution de noms peut ne pas fonctionner. Envisagez d\'utiliser des serveurs DNS personnalisés." + "Impossible d\'ajouter le serveur DNS \"%1$s\", rejetés par le système: %2$s" + "Erreur d\'obtention des paramètres de proxy: %s" + "Utilisation du proxy %1$s %2$d" + "Ignorer" + "Redémarrer" + "Les changements de configuration sont appliquées après redémarrage du VPN. (Re)démarrer le VPN maintenant?" + "Configuration modifiée" + + diff --git a/app/src/main/res/values-id/arrays.xml b/app/src/main/res/values-id/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-id/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml new file mode 100644 index 00000000..ad893b75 --- /dev/null +++ b/app/src/main/res/values-id/strings.xml @@ -0,0 +1,80 @@ + + + Batal + Kode program dan perekam masalah tersedia di + Aplikasi memakai komponen berikut; lihat kode program untuk lebih jelas mengenai lisensi + Tentang... + Tidak ada kesalahan + Gagal menganalisa rute buatan + Hubungkan VPN + Profil di shrotcut tidak ada + Rute ditolak Android + Putus + Bersihkan catatan + Batal Konfirmasi + Putuskan sambungan VPN/Batalkan usaha menyambungkan VPN? + Ubah seting OpenVPN + Pada beberapa setelan manual gambar ICS izin pada/dev/tun mungkin salah, atau modul tun mungkin hilang sepenuhnya. + Gagal membuka layanan antarmuka TUN + "Kesalahan: " + Bersihkan + Info + Detail koneksi + Konfigurasi terakhir dari OpenVPN: + IPv4 lokal : %1$s/%2$d IPv6: %3$s MTU: %4$d + Server DNS : %s + Domain DNS : %s + Rute: %s + Rute IPv6: %s + Memilki informasi antarmuka %1$ s dan %2$, asumsi alamat kedua adalah alamat remote. Menggunakan netmask /32 untuk IP lokal. Mode yang diberikan oleh OpenVPN adalah \"%3$\". + Tidak masuk akal membuat %1$ s dan %2$ s sebagai rute IP dengan netmask CIDR, Gunakan /32 sebagai netmask. + rute yang diperbaiki %1$s/%2$s hingga %3$s/%2$s + Tidak dapat mengakses sertifikat Keychain Android. Dapat disebabkan karena upgrade firmware atau pengembalian backup pengaturan app. Mohon ubah VPN, dan pilih ulang sertifikat berbasis pengaturan dasar agar izin mengakses sertifikat dapat dibuat ulang. + %1$s %2$s + Kirim berkas catatan + Kirim + Berkas catatan LEAP Android + Salin catatan masuk ke clipboard + Gambar Anda tidak mendukung VPNService API, maaf:( + TUN tidak dapat dibuka tanpa informasi IP + Menunggu pesan status… + Profil yang diambil + profil yang diambil %d + Nama pengguna tidak boleh kosong. + Berkas kunci enkripsi PKCS12 + Sandi kunci pribadi + Sandi + Membuat konfigurasi… + Sertifikat didapatkan \'%s\' dari Keystore + Status jaringan: %s + Tidak ada sertifikat CA yang didapat saat membaca dari Android Keystore. Otentifikasi mungkin gagal + Berjalan di %1$s (%2$s) %3$s, Android API %4$d + Kesalahan masuk dengan kunci Android keystore %1$ s: %2$ s + Terjemah Bahasa Indonesia oleh Dayro + Tidak ada server DNS yang digunakan. Resolusi nama mungkin tidak bisa bekerja. Pertimbangkan seting server DNS yang diatur sendiri + Tak bisa menambahkan Server DNS \"%1$ s\", ditolak oleh sistem: %2$ s + Gagal mendapatkan pengaturan proxy: %s + Menggunakan proxy %1$ s %2$ d + Abaikan + Restart + Perubahan konfigurasi baru diterapkan setelah restart VPN. Restart VPN sekarang? + Konfigurasi berubah + + OpenVPN crash tak terduga. Silakan mempertimbangkan mengirim menggunakan pilihan Minidump di Menu Utama + Bitmask - %s + + Menghubungkan + Menunggu jawaban server + Melakukan otentifikasi + Mengambil konfigurasi klien + Menetapkan alamat IP + Menambahkan rute + Terhubung + Menghubungkan kembali + Keluar + Tidak berjalan + Mengenali nama host + Menghubungkan (TCP) + Otentifikasi gagal + Menunggu jaringan yang dapat dipakai + diff --git a/app/src/main/res/values-it/arrays.xml b/app/src/main/res/values-it/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-it/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml new file mode 100644 index 00000000..788dbcc5 --- /dev/null +++ b/app/src/main/res/values-it/strings.xml @@ -0,0 +1,66 @@ + + + Annulla + Il codice sorgente e il bug tracker sono disponibili all\'indirizzo https://github.com/leapcode/bitmask_android/ + Questo programma usa i seguenti componenti; guarda il codice sorgente per i dettagli completi sulle licenze + Informazioni + Nessun errore trovato + Errore nella configurazione + Impossibile analizzare l\'indirizzo IPv4 + Errore durante la lettura delle regole di reindirizzamento (routing) + Connetti alla VPN + Profilo indicato nel collegamento non trovato + Reindirizzamento (route) rifiutato da Android + Scollega + Cancella registro + Conferma l\'annullamento + Disconnetti la VPN in uso/annulli il tentativo di connessione? + Modifica impostazioni VPN + In alcune immagini ICS personalizzate i permessi su /dev/tun potrebbero essere errati, oppure il modulo TUN completamente assente. + Impossibile accedere all\'interfaccia tun + "Errore:" + Azzera + Info + Visualizza i dettagli della connessione + Ultima configurazione interfaccia OpenVPN: + Indirizzi locali - IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + Server DNS: %s + Dominio DNS: %s + Instradamenti (route): %s + Instradamenti (route) IPv6: %s + Ottenute le informazioni sulle interfacce %1$s e %2$s, assumendo che il secondo indirizzo sia il peer remoto. Utilizzata la maschera /32 per l\'IP locale. La modalità impostata da OpenVPN è \"%3$s\". + Impossibile utilizzare %1$s e %2$s come reindirizzamenti IP con la maschera CIDR, è stata quindi usata la maschera /32. + Instradamento %1$s/%2$s corretto con %3$s/%2$s + Impossibile accedere ai certificati della Keychain di Android (può essere causato da un aggiornamento del firmware o dal ripristino di un backup dell\'applicazione o delle sue impostazioni). E\' necessario modificare le impostazioni della VPN e riselezionare il certificato nelle impostazioni principali per ricreare i permessi di accesso al certificato. + %1$s %2$s + Invia il file di log + Invia + File log di LEAP Android + Voce di registro copiata negli appunti + La tua immagine non è supportata dal VPNService API, mi dispiace :( + Rifiuto di attivare il dispositivo tun senza informazioni sull\'IP + In attesa del messaggio di stato... + profilo importato + profilo importato %d + L\'username non deve essere vuoto. + File con la chiave di crittografia PKCS12 + Password della chiave privata + Password + Configurazione in corso... + Ottenuto il certificato \'%s\' dal Keystore + Stato della rete: %s + Nessun certificato della CA è stato prelevato dal Keystore di Android. E\' probabile che l\'autenticazione fallisca. + In esecuzione su %1$s (%2$s) %3$s, Android API %4$d + Errore di firma con la chiave %1$s: %2$s del Keystore di Android. + Traduzione in inglese di Arne Schwabe<arne@rfc2549.org> + Nessun server DNS in uso. La risoluzione dei nomi potrebbe non funzionare. Valuta se inserire dei server DNS personalizzati. + Impossibile aggiungere il server DNS \"%1$s\", respinto dal sistema: %2$s + Errore nell\'ottenere le impostazioni del proxy: %s + Si sta utilizzando il proxy %1$s %2$d + Ignora + Riavvia + Le modifiche sarannoi applicate dopo aver riavviato la connessione VPN. Riavviare ora la connessione? + Configurazione modificata + + OpenVPN si è arrestato in modo imprevisto. Ti consigliamo di attivare l\'opzione Invia Minidump nel menu principale. + diff --git a/app/src/main/res/values-ja/arrays.xml b/app/src/main/res/values-ja/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-ja/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 00000000..4503c227 --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,82 @@ + + + キャンセル + ソースコードと問題管理は以下で: https://github.com/leapcode/bitmask_android/ + プログラムは、次のコンポーネントを使用します。完全な詳細についてはソース上のライセンスを参照してください。 + バージョン情報 + エラーは見つかりませんでした。 + 設定に誤りがあります。 + IPv4 アドレスの解析エラー + カスタム経路の解析エラー + VPNに接続 + ショートカットで指定されたプロファイルが見つかりません + 経路がAndroidにより拒否されました。 + 切断 + ログをクリア + キャンセルの確認 + 接続中または試行中の接続をキャンセルしますか? + VPN 設定の編集 + いくつかのカスタムICSイメージは、/dev/tunのパーミッションが異常か、TUNモジュールが含まれていません。 + TUNデバイスを開こうとして失敗しました。 + "エラー:" + クリア + 情報 + 接続の詳細を表示 + OpenVPNから設定した最後のインターフェイス: + ローカル IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS サーバー: %s + DNS ドメイン: %s + 経路:%s + 経路 IPv6:%s + インターフェース情報として[%1$s]と[%2$s]を取得しました。2つめのアドレスはリモート側のピアアドレスです。32ビットマスクをローカルIPに使用します。 OpenVPNのモードは[%3$s]です。 + %1$sと%2$sではCIDR形式のIP経路情報として意味をなしません。32ビットマスクを使用します。 + 経路情報%1$s/%2$sを%3$s/%2$sに修正しました。 + Androidの証明書管理にアクセスできません。(ファームウェアの更新、アプリケーションまたはその設定のリストアによって発生する場合があります)。VPNの設定で証明書の選択を再度行ってください。 + %1$s %2$s + ログ ファイルを送信します。 + 送信 + LEAP Android ログ ファイル + クリップ ボードにコピーされたログ エントリ + 申し訳ありませんが、お使いの環境ではVPNサービスがサポートされていません。 + IP情報なしでのTUNデバイス使用は拒否しています + 状態メッセージを待っています。 + インポートされたプロファイル + インポートされたプロファイル %d + ユーザ名を空に設定することはできません + PKCS12ファイルの暗号化キー + 秘密鍵のパスワード + パスワード + 構成中・・・ + \'%s\'の証明書をキーストアから読み出し + ネットワーク状態: %s + 認証局証明書(CA Cert)がAndroidのキーストアから取得できませんでした。認証はおそらく失敗します。 + 実行中:%1$s (%2$s)%3$s Android API %4$d + Androidキーストアに保存されたキー %1$s: %2$sの署名エラーです + 日本語翻訳 高橋正希@埼玉 <tools@artin.nu> + 使用されている DNS サーバーはありません。名前解決は動作しません。DNSサーバーの設定を見直してください。 + DNSサーバ \"%1$s\" の追加に失敗しました。%2$sに拒否されました。 + プロキシ設定でエラー: %s + プロキシを使用します %1$s %2$d + 無視 + 再起動 + 設定の変更はVPNの再起動後に反映されます。VPNを(再)起動しますか? + 設定が変更されました + + OpenVPN は予期せず終了しました。メイン メニューでミニダンプの送信オプションを検討してください。 + Bitmask - %s + + 接続中 + サーバーの応答を待っています。 + 認証中 + クライアントの構成を取得中 + IPアドレスを割り当て中 + 経路を追加中 + 接続しました + 再接続中 + 終了中 + 停止中 + ホスト名を解決中 + 接続中(TCP) + 認証に失敗しました + 使用可能なネットワークを待機中 + diff --git a/app/src/main/res/values-ko/arrays.xml b/app/src/main/res/values-ko/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-ko/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml new file mode 100644 index 00000000..8a725bad --- /dev/null +++ b/app/src/main/res/values-ko/strings.xml @@ -0,0 +1,65 @@ + + + 소스 코드와 문제 추적기는 https://github.com/leapcode/bitmask_android/에서 사용할 수 있습니다 + 프로그램은 다음 구성 요소를 사용합니다. 라이선스에 대 한 자세한 내용은 소스를 참조 하십시오 + 소개 + 오류 없음 + 설정 오류 + IPv4 주소 구문 분석 오류 + 사용자 지정 경로 구문 분석 오류 + VPN에 연결 하기 + 바로 가기에 지정 된 프로파일을 찾을 수 없습니다. + 안드로이드에 의해 거부된 라우트 + 연결 끊기 + 로그 지우기 + 취소 확인 + 연결된 VPN 끊기/연결시도 취소? + VPN 설정 편집 + 일부 사용자 지정 ICS 이미지에서는 /dev/tun에 대한 권한이 잘못 되어 있거나 tun 모듈 자체가 누락 될 수 있습니다. + Tun 인터페이스를 열지 못했습니다 + "오류:" + 지우기 + 정보 + 연결 세부 정보 보기 + Openvpn에서 마지막 인터페이스 구성: + 로컬 IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS 서버: %s + DNS 도매인: %s + 라우트: %s + 라우트 IPv6: %s + 인터페이스 정보 %1$s와 %2$s에 있어, 두 번째 주소를 원격 피어 주소로 가정 하겠습니다. 로컬 IP의 넷마스크로는 /32를 사용하겠습니다. OpenVPN에 의해 주어진 모드는 \"%3$s\" 입니다. + CIDR 넷마스크가 있는 IP 라우트 %1$s 와 %2$s 에 있어서 처리가 불가능합니다. /32를 넷마스크로 사용하겠습니다. + %1$s/%2$s 에서 %3$s/%2$s로 라우트 수정 + 안드로이드 키체인 인증서에 접근할 수 없습니다. 펌웨어 업그래이드 또는 백업된 앱/앱 설정을 복구하면서 발생할 수 있습니다. 인증서에 액세스할 수 있는 권한을 다시 생성하기 위해 VPN을 편집 하고 기본 설정 아래에서 인증서를 다시 선택 하십시오. + %1$s %2$s + 로그 파일 보내기 + 보내기 + LEAP 로그 파일 + 클립보드로 로그 복사 + 당신의 이미지는 VPNService API를 지원 하지 않습니다, 죄송 합니다:( + IP 정보가 없는 tun 장치 열기를 거부합니다 + 상태 메시지를 기다리는 중... + 가져온 프로파일 + 가져온 프로파일 %d + 사용자 이름이 비어 있지 않아야 합니다. + PKCS12 파일 암호화 키 + 개인 키 암호 + 암호 + 설정 만드는중… + Keystore에서 인증서 \'%s\' 받음 + 네트워크 상태: %s + 안드로이드 keystore에서 CA 인증서를 찾지 못했습니다. Auhtentication은 실패할 것 입니다. + %1$s (%2$s) %3$s, 안드로이드 API %4$d 에서 실행 + 안드로이드 keystore 키 %1$s: %2$s과 싸이닝 오류 + 한국어 번역 (주)기가드 안규태<ktdann@gmail.com> + 사용 중인 DNS 서버가 없습니다. 이름 확인이 작동 하지 않을 수 있습니다. 사용자 지정 DNS 서버의 사용을 고려해 보세요. + DNS서버 \"%1$s\" 는, 시스템에 의해 거부되 추가할 수 없습니다: %2$s + 프록시 설정 가져오기 오류: %s + 프록시 %1$s %2$d 을 사용 + 무시 + 다시 시작 + VPN을 다시 시작한 후 설정 변경 내용이 적용 됩니다. VPN을 지금 (재)시작? + 설정 변경 + + OpenVPN이 예기치 않게 종료됐습니다. 메인 메뉴에 있는 미니 덤프 보내기 옵션의 사용을 고려 하시기 바랍니다 + diff --git a/app/src/main/res/values-nl/arrays.xml b/app/src/main/res/values-nl/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-nl/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml new file mode 100644 index 00000000..0442b054 --- /dev/null +++ b/app/src/main/res/values-nl/strings.xml @@ -0,0 +1,33 @@ + + + Over + Geen fout. + Fout in de configuratie + Met VPN verbinden + Route geweigert door Android + Verbinding verbreken + logboek wissen + Annuleer bevestiging + Sluit de verbonden VPN af/annuleer de verbindingspoging? + VPN Instellingen Bewerken + "Fout:" + Leeg maken + info + Details van de verbinding weergeven + Laatste interfaceconfiguratie van OpenVPN: + Lokaal IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS Server: %s + DNS Domein: %s + Routes: %s + Routes IPv6: %s + %1$s %2$s + Logboek verzenden + Verzenden + Wachten op status bericht… + Geïmporteerd profiel + Geïmporteerd profiel %d + De gebruikersnaam moet niet leeg zijn. + PKCS12 Bestand Encryptie Sleutel + Privé Sleutel Wachtwoord + Wachtwoord + diff --git a/app/src/main/res/values-no/arrays.xml b/app/src/main/res/values-no/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-no/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml new file mode 100644 index 00000000..a363b2f9 --- /dev/null +++ b/app/src/main/res/values-no/strings.xml @@ -0,0 +1,38 @@ + + + Om + Ingen feil funnet + Feil i konfigurasjonen + Koble til VPN + Koble fra + Tøm logg + Avbryt bekreftelse + Rediger VPN-innstillinger + "Feil:" + Fjern + info + Vis Tilkoblingsdetaljer + DNS-server: %s + DNS-domene: %s + Ruter: %s + %1$s %2$s + Send loggfilen + Send + LEAP Android loggfil + Venter på tilstands melding... + importert profil + importert profilen %d + Brukernavnet kan ikke være tomt. + PKCS12 Filkrypteringsnøkkel + Privat nøkkel passord + Passord + Lager konfigurasjon... + Nettverksstatus: %s + Kjører på %1$s (%2$s) %3$s, Android API %4$d + Norsk oversettelse av Jonny + Feil ved henting av proxy-innstillinger: %s + Bruker proxy %1$s %2$d + Ignorer + Start på nytt + Konfigurasjon endret + diff --git a/app/src/main/res/values-ro/arrays.xml b/app/src/main/res/values-ro/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-ro/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml new file mode 100644 index 00000000..22496320 --- /dev/null +++ b/app/src/main/res/values-ro/strings.xml @@ -0,0 +1,51 @@ + + + Cod sursă şi tracker probleme disponibile la https://github.com/leapcode/bitmask_android/ + Acest program utilizează următoarele componente; a se vedea codul sursă pentru mai multe detalii despre licente + Despre + Nu s-a găsit nici o eroare + Eroare în configurare + Eroare parsare adresă IPv4 + Eroare parsare rute particularizate + Conectare la VPN + Profilul specificat în comanda rapidă nu a fost găsit + Rută respinsă de Android + Deconectaţi + Golire jurnal + Anulare confirmare + Deconectaţi VPN-ul conectat/anulaţi încercarea de conectare? + Editaţi setările VPN + În cazul anumitor imagini particularizate de ICS permisiile pentru /dev/tun pot fi greşite sau modulul tun lipseşte cu desăvârşire. + Eroare deschidere interfaţa tun + "Eroare:" + Goleşte + info + Arată detaliile conexiunii + Ultima configurare interfaţă din OpenVPN: + Local IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + Server DNS: %s + Domeniu DNS: %s + Rute: %s + Rute IPv6: %s + S-au primit informaţiile despre interfaţă %1$s şi %2$s, presupun că a doua adresă este adresa peer a serverlui remote. Folosesc netmask /32 pentru IP local. Modul dat de OpenVPN este \"%3$s\". + %1$s %2$s + Trimite fişier jurnal + Trimite + Fişier jurnal LEAP Android + profil importat + profil importat %d + Cheie criptare fişier PKCS12 + Parola cheie privată + Parola + Se generează configurarea… + Am primit certificatul \'%s\' din Keystore + Statutus reţea: %s + Rulează pe %1$s (%2$s) %3$s, Android API %4$d + Eroare semnare cu Android keystore key %1$s: %2$s + Folosesc proxy %1$ s %2$ d + Ignora + Restart + Configuraţie schimbată + + OpenVPN sa oprit în mod neaşteptat. Vă rugăm să consideraţi opţiunea de trimitere a unui Minidump din meniul principal + diff --git a/app/src/main/res/values-ru/arrays.xml b/app/src/main/res/values-ru/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-ru/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml new file mode 100644 index 00000000..aaa42690 --- /dev/null +++ b/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,80 @@ + + + Данная программа использует следующие компоненты; смотрите исходный код для получения подробной информации о лицензии + О программе + Ошибок не найдено + Ошибка в конфигурации + Невозможно прочесть IPv4 адрес + Невозможно примениить пользовательские маршруты + Подключиться к VPN + Не найден профиль, указанный в ярлыке + Маршрут отвергнут Android + Отключение + очистить журнал + Подтверждение отмены + Отключение активных VPN/Отмена попыток подключения? + Редактирование параметров VPN + На некторых костомных сборках права на /dev/tun могут быть неверными или tun-модуль может быть не включен. Для прошивки CM9 можете попробовать исправить владельца прямо из настроек программы + Открытие интерфейса tun окончилось неудачей + "Ошибка: " + Очистить + информация + Показать подробности о подключении + Последняя используемая конфигурация OpenVPN: + Адрес IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS-сервер: %s + DNS-домен: %s + Маршруты: %s + Маршруты IPv6: %s + Получена информация интерфейса %1$s и %2$s, второй адрес является удалённым адресом канала. Используется сетевая маска /32 для локального IP адреса. Режим, установленный OpenVPN: \"%3$s\". + Невозможно использовать выражения %1$s и %2$s как маршрут по стандарту CIDR. используется /32 как маска подсети. + Маршрут исправлен с %1$s/%2$s на %3$s/%2$s + Не удается получить доступ к хранилищу ключей и сертификатов Android. Это может быть вызвано обновлением прошивки или восстановления старой копии приложения или его настроек. Пожалуйста, отредактируйте профиль VPN и заново укажите ключи и сертификаты в разделе Основные параметры. + %1$s %2$s + Отправить файл журнала + Отправить + LEAP Android файла лога + Скопировать лог в буфер обмена + Ваша прошивка не поддерживает VPNService API, извините :( + Отказ в открытии устройства tun без информации об IP-адресе + Ожидание сообщения о состоянии… + импортируемый профиль + импортируемый профиль %d + Имя пользователя не должно быть пустым. + Файл PKCS12-ключа + Пароль закрытого ключа + Пароль + Создание конфигурации… + Получен сертификат \'%s\' из хранилища ключей + Статус сети: %s + Не удалось получить CA из хранилища ключей Android. Аутентификация не удалась. + Работает на %1$s (%2$s) %3$s, Android API %4$d + Ошибка подписи с использованием ключа из хранилища Android %1$s: %2$s + Русский перевод от RusFox <horonitel@gmail.com> + DNS-серверы не используются. Разрешение имен может не работать. Рассмотрите возможность указания DNS-серверов + Не удалось добавить DNS-сервер \"%1$s\", отклонен системой: %2$s + Ошибка при получении параметров прокси-сервера: %s + Используется прокси-сервер %1$s %2$d + Игнорировать + Перезагрузка + Изменения конфигурации применяются после перезапуска VPN. (Пере)запустить VPN теперь? + Конфигурация изменена + + OpenVPN завершилась неожиданно. Пожалуйста, посмотрите опцию \"Отправить Minidump\" в главном меню + Bitmask - %s + + Подключение + Ожидание ответа сервера + Проверка подлинности + Получение конфигурации клиента + Назначение IP-адресов + Добавление маршрутов + Подключено + Повторное подключение + Выход + Не запущено + Разрешение имен узлов + Подключение (TCP) + Ошибка аутентификации + Ожидание работы сети + diff --git a/app/src/main/res/values-uk/arrays.xml b/app/src/main/res/values-uk/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-uk/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml new file mode 100644 index 00000000..dab28b48 --- /dev/null +++ b/app/src/main/res/values-uk/strings.xml @@ -0,0 +1,81 @@ + + + Початковий код і відстеження проблем доступні по https://github.com/leapcode/bitmask_android/ + Ця програма використовує такі компоненти; перегляньте вихідний код для повної інформації про ліцензії + Про + Помилок не знайдено + Помилка конфігурації + Помилка при розборі адреси IPv4 + Помилка аналізу налаштованих маршрутів + Підключення до VPN + Профіль, вказаний у ярлику, не знайдено + Маршрут відхилено Андроїдом + Від\'єднати + очистити журнал + Підтвердження скасування + Відключення активних VPN/скасувати спробу підключення? + Змінити налаштування VPN + На деяких користувацьких прошивках ICS права на /dev/tun можуть бути невірними або модуль tun може бути взагалі відсутнім. + Не вдалося відкрити інтерфейс tun + "Помилка: " + Очистити + Інформація + Показати відомості про підключення + Останній інтерфейс конфігурації з OpenVPN: + Адреса IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS-сервер: %s + DNS домен: %s + Маршрути: %s + Маршрути IPv6: %s + Отримано інформацію інтерфейсу %1$s і %2$s, друга адреса є віддаленою адресою каналу. Використовується мережева маска /32 для локальної IP-адреси. Режим, встановлений OpenVPN: \"%3$s\". + Неможливо використовувати вирази %1$s і %2$s як маршрут за стандартом CIDR. використовується /32 як маска підмережі. + Виправлено маршрут з %1$s/%2$s на %3$s/%2$s + Не можна отримати доступ до сховища ключів та сертифікатів Андроїд. Це можливо спричинено оновлення прошивки або відновленням резервної копії програми чи її налаштувань. Будь ласка, відредагуйте профіль VPN та заново виберіть сертифікат у основних параметрах для створення доступу до сертифікату. + %1$s %2$s + Надіслати файл журналу + Надіслати + LEAP Android файл журналу + Скопійовано запис журналу до буферу обміну + Ваша прошивка не підтримує VPNService API, вибачте :( + Відмова у відкритті пристрою tun без інформації про IP-адресу + Очікування повідомлення стану... + імпортований профіль + імпортований профіль: %d + Ім\'я користувача не може бути порожнім. + PKCS12 Ключ шифрування файлу + Пароль закритого ключа + Пароль + Побудова конфігурації… + Отримано сертифікат \'%s\' з сховища ключів + Статус мережі: %s + Не вдалося отримати СА сертифікат при читанні із сховища ключів Андроїд. Автентифікація не вдалася. + Працює на %1$s (%2$s) %3$s, Android API %4$d + Помилка підпису з використанням ключа із сховища Андроїд %1$s: %2$s + Переклад українською від wvolov + Жодний DNS сервер не використовується. Розширення імен можуть не працювати. Розгляньте можливість вказання DNS серверів + Не можливо додати DNS-сервер \"%1$s\", відхилено системою: %2$s + Помилка отримання параметрів проксі: %s + Використовується проксі %1$s %2$d + Ігнорувати + Перезапустити + Після перезапуску VPN застосувати зміни конфігурації. (Пере)запустити VPN тепер? + Конфігурація змінена + + OpenVPN впав несподівано. Будь ласка, розгляньте використання параметру \"Надіслати Мінідамп\" в головному меню + Bitmask - %s + + Підключення + Очікування відповіді сервера + Аутентифікація + Отримання конфігурації клієнта + Перепризначення IP-адрес + Додавання маршрутів + Підключено + Повторне підключення + Виходимо + Не працює + Розпізнавання імен вузлів + Підключення (TCP) + Помилка автентифікації + Очікування на використання мережі + diff --git a/app/src/main/res/values-zh-rCN/arrays.xml b/app/src/main/res/values-zh-rCN/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-zh-rCN/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..ae57d277 --- /dev/null +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,52 @@ + + + 请前往 https://github.com/leapcode/bitmask_android/ 源码或提供问题反馈 + 本程序使用以下组件,请在 Licenses 查看源码获取更详细内容。 + 关于 + 未找到错误 + 配置有错误 + 无法解析 IPv4 地址 + 无法解析自定义路由 + 连接到 VPN + 未找到快捷方式中指定的配置文件 + Android 拒绝了路由 + 断开 + 清除日志 + 取消确认 + 断开已连接的 VPN / 取消连接尝试? + 编辑 VPN 设置 + 未能打开 tun 模块 + 错误 + 清除 + 信息 + 显示连接信息 + 最后 OpenVPN 接口配置: + 本地 IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS 服务器: %s + DNS 域: %s + IPv4 路由: %s + IPv6 路由: %s + %1$s %2$s + 发送日志文件 + 发送 + LEAP Android 日志文件 + 日志条目已复制剪贴板 + 无 IP 信息,拒绝打开 tun 设备 + 等待状态消息 + 已导入配置文件 + 已导入配置文件 %d + 用户名不能为空。 + PKCS12 文件加密密钥 + 私钥密码 + 密码 + 正在生成配置 + 网络状态: %s + 运行 %1$s ( %2$s ) %3$s ,Android API %4$d + 中文翻译: 白达卫 +<59539051+ovpntrans.zh@mail.dcu.ie> + 获取代理设置时出错:%s + 使用代理 %1$s %2$d + 忽略 + 重启 + 配置已更改 + diff --git a/app/src/main/res/values-zh-rTW/arrays.xml b/app/src/main/res/values-zh-rTW/arrays.xml new file mode 100644 index 00000000..045e125f --- /dev/null +++ b/app/src/main/res/values-zh-rTW/arrays.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..880fb4ad --- /dev/null +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,52 @@ + + + 取得原始碼與個案追蹤,可上 https://github.com/leapcode/bitmask_android/ + 本程序使用了以下元件,其作者和授權資訊如下 + 關於 + 未有找到錯誤 + 設定中含有錯誤 + 解析IPv4地址時發生錯誤 + 解析自訂路由時發生錯誤 + 連接到VPN + 在快捷方式找不到指定的設定檔 + 路由被Android拒絕 + 斷線 + 清除記錄檔 + 確認取消 + 編輯VPN設定 + 一些自訂的Android4.0 ROM存在/dev/tun的擁有者權限問題,甚至完全沒有Tun模組。CM9用家請嘗試於\"全域設置\"下修正Tun擁有者。 + 無法開啟Tun網絡介面 + "錯誤: " + 清除 + 資訊 + 顯示連線的詳細資訊 + 本地IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS伺服器: %s + DNS網域: %s + 路徑: %s + IPv6路由: %s + %1$s %2$s + 分享記錄檔 + 分享 + LEAP Android 記錄檔 + 已將記錄複製到剪貼簿 + 你的Android ROM不支援VPN服務API,抱歉了。 :( + 等待狀態訊息... + 使用者名稱不能為空。 + PKCS12檔加密金鑰 + 私密金鑰密碼 + 密碼 + 正在生成設定檔… + 網絡狀態: %s + 於 %1$s (%2$s) %3$s 上運行, Android API 版本: %4$d + 繁體中文 由 羊羊@自由網絡研究中心 <sora8964@gmail.com> 翻譯 + 沒有任何DNS伺服器可用,可能無法進行網域名稱解析。請考慮設置自訂的DNS伺服器 + 取得代理伺服器資訊時發生錯誤: %s + 使用代理伺服器 %1$s %2$d + 忽略 + 重置 + 配置變更只會在重新啟動VPN時才生效,現在要(重新)啟動VPN嗎? + 設定已變更 + + OpenVPN非預期地崩潰,你或者會考慮在主選單下傳送Minidump給開發人員。 + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 00000000..66f07a87 --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 00000000..cb503b86 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..492c48c7 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,142 @@ + + + + Cancel + OK + Retry + Source code and issue tracker available at https://github.com/leapcode/bitmask_android/ + Translations welcome and appreciated. See our Transifex project at https://www.transifex.com/projects/p/bitmask-android/ + This program uses the following components; see the source code for full details on the licenses + About Bitmask + Switch provider + No error found + Error in Configuration + Error parsing the IPv4 address + Error parsing the custom routes + Connect to VPN + Profile specified in shortcut not found + Route rejected by Android + Disconnect + clear log + Cancel Confirmation + Disconnect the connected VPN/cancel the connection attempt? + Edit VPN Settings + On some custom ICS images the permission on /dev/tun might be wrong, or the tun module might be missing completely. + Failed to open the tun interface + "Error: " + Clear + info + Show connection details + Last interface configuration from OpenVPN: + Local IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d + DNS Server: %s + DNS Domain: %s + Routes: %s + Routes IPv6: %s + Got interface information %1$s and %2$s, assuming second address is peer address of remote. Using /32 netmask for local IP. Mode given by OpenVPN is \"%3$s\". + Cannot make sense of %1$s and %2$s as IP route with CIDR netmask, using /32 as netmask. + Corrected route %1$s/%2$s to %3$s/%2$s + Cannot access the Android Keychain Certificates. This can be caused by a firmware upgrade or by restoring a backup of the app/app settings. Please edit the VPN and reselect the certificate under basic settings to recreate the permission to access the certificate. + %1$s %2$s + Send log file + Send + Bitmask OpenVPN log file + Copied log entry to clip board + Your image does not support the VPNService API, sorry :( + Refusing to open tun device without IP information + Waiting for state message… + imported profile + imported profile %d + The username must not be empty. + PKCS12 File Encryption Key + Private Key Password + Password + Building configuration… + Got certificate \'%s\' from Keystore + Network Status: %s + No CA Certificate returned while reading from Android keystore. Authentication will probably fail. + Running on %1$s (%2$s) %3$s, Android API %4$d + Error signing with Android keystore key %1$s: %2$s + English translation by Arne Schwabe <arne@rfc2549.org> + No DNS servers being used. Name resolution may not work. Consider setting custom DNS Servers + Could not add DNS Server \"%1$s\", rejected by the system: %2$s + Error getting proxy settings: %s + Using proxy %1$s %2$d + Ignore + Restart + Configuration changes are applied after restarting the VPN. (Re)start the VPN now? + Configuration changed + OpenVPN crashed unexpectedly. Please consider using the send Minidump option in the main menu + Bitmask - %s + Connecting + Waiting for server reply + Authenticating + Getting client configuration + Assigning IP addresses + Adding routes + Connected + Reconnecting + Exiting + Not running + Resolving host names + Connecting (TCP) + Authentication failed + Waiting for usable network + Settings + Bitmask + Provider: + No provider configured + Access EIP connection settings + Status unknown. + Connection will be secure using an anonymous certificate. + Connection secure using an anonymous certificate. + Connection will be secure using your own certificate. + Connection secure using your own certificate. + Encrypted Internet + Select a service provider + Add new Provider + Add a new service provider + Save + Domain name + It seems your URL is well formed + It seems your URL is not well formed + Provider details + Use anonymously + username + Please enter your username + password + User message + About Bitmask" + Try again: server math error. + Incorrect username or password. + It should have at least 8 characters. + Try again: Client HTTP error + Try again: I/O error + Try again: Bad response from the server + Update the app + Log In + Log Out + Configuration Error + Configure + Exit + There was an error configuring Bitmask with your chosen provider.\n\nYou may choose to reconfigure, or exit and configure a provider upon next launch. + Server is unreachable, please try again. + It doesn\'t seem to be a Bitmask provider. + This is not a trusted Bitmask provider. + Configuring provider + Your anon cert was not downloaded + Logging in + Logging out from this session. + Didn\'t logged out. + Authentication succeeded. + Authentication failed. + Your own cert has been correctly downloaded. + Your own cert has incorrectly been downloaded. + Initiating connection + Cancel connection? + There is a connection attempt in progress. Do you wish to cancel it? + Yes + No + "Not running! Connection not secure!" + Connection Secure. + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..1c20cbcd --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/untranslatable.xml b/app/src/main/res/values/untranslatable.xml new file mode 100644 index 00000000..6435bfbf --- /dev/null +++ b/app/src/main/res/values/untranslatable.xml @@ -0,0 +1,19 @@ + + + Bitmask + Bitmask + + Copyright 2012\nLEAP Encryption Access Project <info@leap.se> + Copyright © 2002–2010 OpenVPN Technologies, Inc. <sales@openvpn.net>\n + + "OpenVPN" is a trademark of OpenVPN Technologies, Inc. + Copyright © 1996 – 2011 Markus Franz Xaver Johannes Oberhumer + This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit\n + Copyright © 1998-2008 The OpenSSL Project. All rights reserved.\n\n + This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)\n + Copyright © 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved. + OpenVPN + LZO + OpenSSL + Unknown state + diff --git a/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java b/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java new file mode 100644 index 00000000..15a135d2 --- /dev/null +++ b/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java @@ -0,0 +1,574 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + + + + + + +import android.app.Activity; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.Display; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View.MeasureSpec; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; +import org.json.JSONException; +import org.json.JSONObject; +import se.leap.bitmaskclient.DownloadFailedDialog.DownloadFailedDialogInterface; +import se.leap.bitmaskclient.NewProviderDialog.NewProviderDialogInterface; +import se.leap.bitmaskclient.ProviderAPIResultReceiver.Receiver; +import se.leap.bitmaskclient.ProviderDetailFragment.ProviderDetailFragmentInterface; +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; +import se.leap.bitmaskclient.R; + +/** + * Activity that builds and shows the list of known available providers. + * + * It also allows the user to enter custom providers with a button. + * + * @author parmegv + * + */ +public class ConfigurationWizard extends Activity +implements ProviderListFragment.Callbacks, NewProviderDialogInterface, ProviderDetailFragmentInterface, DownloadFailedDialogInterface, Receiver { + + private ProgressBar mProgressBar; + private TextView progressbar_description; + private ProviderListFragment provider_list_fragment; + private Intent mConfigState = new Intent(); + + final public static String TAG = "se.leap.bitmaskclient.ConfigurationWizard"; + final public static String TYPE_OF_CERTIFICATE = "type_of_certificate"; + final public static String ANON_CERTIFICATE = "anon_certificate"; + final public static String AUTHED_CERTIFICATE = "authed_certificate"; + + final protected static String PROVIDER_SET = "PROVIDER SET"; + final protected static String SERVICES_RETRIEVED = "SERVICES RETRIEVED"; + + public ProviderAPIResultReceiver providerAPI_result_receiver; + private ProviderAPIBroadcastReceiver_Update providerAPI_broadcast_receiver_update; + + private static SharedPreferences preferences; + private static boolean setting_up_provider = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + preferences = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE); + + setContentView(R.layout.configuration_wizard_activity); + mProgressBar = (ProgressBar) findViewById(R.id.progressbar_configuration_wizard); + mProgressBar.setVisibility(ProgressBar.INVISIBLE); + progressbar_description = (TextView) findViewById(R.id.progressbar_description); + progressbar_description.setVisibility(TextView.INVISIBLE); + providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); + providerAPI_result_receiver.setReceiver(this); + providerAPI_broadcast_receiver_update = new ProviderAPIBroadcastReceiver_Update(); + IntentFilter update_intent_filter = new IntentFilter(ProviderAPI.UPDATE_PROGRESSBAR); + update_intent_filter.addCategory(Intent.CATEGORY_DEFAULT); + registerReceiver(providerAPI_broadcast_receiver_update, update_intent_filter); + + loadPreseededProviders(); + + // Only create our fragments if we're not restoring a saved instance + if ( savedInstanceState == null ){ + // TODO Some welcome screen? + // We will need better flow control when we have more Fragments (e.g. user auth) + provider_list_fragment = ProviderListFragment.newInstance(); + Bundle arguments = new Bundle(); + int configuration_wizard_request_code = getIntent().getIntExtra(Dashboard.REQUEST_CODE, -1); + if(configuration_wizard_request_code == Dashboard.SWITCH_PROVIDER) { + arguments.putBoolean(ProviderListFragment.SHOW_ALL_PROVIDERS, true); + } + provider_list_fragment.setArguments(arguments); + + FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.beginTransaction() + .replace(R.id.configuration_wizard_layout, provider_list_fragment, ProviderListFragment.TAG) + .commit(); + } + + // TODO: If exposing deep links into your app, handle intents here. + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(providerAPI_broadcast_receiver_update); + } + + public void refreshProviderList(int top_padding) { + ProviderListFragment new_provider_list_fragment = new ProviderListFragment(); + Bundle top_padding_bundle = new Bundle(); + top_padding_bundle.putInt(ProviderListFragment.TOP_PADDING, top_padding); + new_provider_list_fragment.setArguments(top_padding_bundle); + + FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.beginTransaction() + .replace(R.id.configuration_wizard_layout, new_provider_list_fragment, ProviderListFragment.TAG) + .commit(); + } + + @Override + public void onReceiveResult(int resultCode, Bundle resultData) { + if(resultCode == ProviderAPI.PROVIDER_OK) { + mConfigState.setAction(PROVIDER_SET); + + if (preferences.getBoolean(EIP.ALLOWED_ANON, false)){ + mConfigState.putExtra(SERVICES_RETRIEVED, true); + downloadAnonCert(); + } else { + mProgressBar.incrementProgressBy(1); + mProgressBar.setVisibility(ProgressBar.GONE); + progressbar_description.setVisibility(TextView.GONE); + setResult(RESULT_OK); + showProviderDetails(getCurrentFocus()); + } + } else if(resultCode == ProviderAPI.PROVIDER_NOK) { + //refreshProviderList(0); + String reason_to_fail = resultData.getString(ProviderAPI.ERRORS); + showDownloadFailedDialog(getCurrentFocus(), reason_to_fail); + mProgressBar.setVisibility(ProgressBar.GONE); + progressbar_description.setVisibility(TextView.GONE); + preferences.edit().remove(Provider.KEY).commit(); + setting_up_provider = false; + setResult(RESULT_CANCELED, mConfigState); + } + else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { + mProgressBar.incrementProgressBy(1); + mProgressBar.setVisibility(ProgressBar.GONE); + progressbar_description.setVisibility(TextView.GONE); + //refreshProviderList(0); + setResult(RESULT_OK); + showProviderDetails(getCurrentFocus()); + } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { + //refreshProviderList(0); + mProgressBar.setVisibility(ProgressBar.GONE); + progressbar_description.setVisibility(TextView.GONE); + //Toast.makeText(getApplicationContext(), R.string.incorrectly_downloaded_certificate_message, Toast.LENGTH_LONG).show(); + setResult(RESULT_CANCELED, mConfigState); + } else if(resultCode == AboutActivity.VIEWED) { + // Do nothing, right now + // I need this for CW to wait for the About activity to end before going back to Dashboard. + } + } + + /** + * Callback method from {@link ProviderListFragment.Callbacks} + * indicating that the item with the given ID was selected. + */ + @Override + public void onItemSelected(String id) { + //TODO Code 2 pane view + // resetOldConnection(); + ProviderItem selected_provider = getProvider(id); + int provider_index = getProviderIndex(id); + + + startProgressBar(provider_index+1); + provider_list_fragment.hideAllBut(provider_index); + + setUpProvider(selected_provider.providerMainUrl()); + } + + @Override + public void onBackPressed() { + if(setting_up_provider) { + stopSettingUpProvider(); + } else { + usualBackButton(); + } + } + + private void stopSettingUpProvider() { + ProviderAPI.stop(); + mProgressBar.setVisibility(ProgressBar.GONE); + mProgressBar.setProgress(0); + progressbar_description.setVisibility(TextView.GONE); + getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit().remove(Provider.KEY).commit(); + setting_up_provider = false; + showAllProviders(); + } + + private void usualBackButton() { + try { + boolean is_provider_set_up = new JSONObject(preferences.getString(Provider.KEY, "no provider")) != null ? true : false; + boolean is_provider_set_up_truly = new JSONObject(preferences.getString(Provider.KEY, "no provider")).length() != 0 ? true : false; + if(!is_provider_set_up || !is_provider_set_up_truly) { + askDashboardToQuitApp(); + } else { + setResult(RESULT_OK); + } + } catch (JSONException e) { + askDashboardToQuitApp(); + super.onBackPressed(); + e.printStackTrace(); + } + super.onBackPressed(); + } + private void askDashboardToQuitApp() { + Intent ask_quit = new Intent(); + ask_quit.putExtra(Dashboard.ACTION_QUIT, Dashboard.ACTION_QUIT); + setResult(RESULT_CANCELED, ask_quit); + } + + private ProviderItem getProvider(String name) { + Iterator providers_iterator = ProviderListContent.ITEMS.iterator(); + while(providers_iterator.hasNext()) { + ProviderItem provider = providers_iterator.next(); + if(provider.name().equalsIgnoreCase(name)) { + return provider; + } + } + return null; + } + + private String getId(String provider_main_url_string) { + try { + URL provider_url = new URL(provider_main_url_string); + URL aux_provider_url; + Iterator providers_iterator = ProviderListContent.ITEMS.iterator(); + while(providers_iterator.hasNext()) { + ProviderItem provider = providers_iterator.next(); + aux_provider_url = new URL(provider.providerMainUrl()); + if(isSameURL(provider_url, aux_provider_url)) { + return provider.name(); + } + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return ""; + } + + /** + * Checks, whether 2 urls are pointing to the same location. + * + * @param url a url + * @param baseUrl an other url, that should be compared. + * @return true, if the urls point to the same host and port and use the + * same protocol, false otherwise. + */ + private boolean isSameURL(final URL url, final URL baseUrl) { + if (!url.getProtocol().equals(baseUrl.getProtocol())) { + return false; + } + if (!url.getHost().equals(baseUrl.getHost())) { + return false; + } + if (url.getPort() != baseUrl.getPort()) { + return false; + } + return true; + } + + private void startProgressBar() { + mProgressBar.setVisibility(ProgressBar.VISIBLE); + mProgressBar.setProgress(0); + mProgressBar.setMax(3); + } + + private void startProgressBar(int list_item_index) { + mProgressBar.setVisibility(ProgressBar.VISIBLE); + progressbar_description.setVisibility(TextView.VISIBLE); + mProgressBar.setProgress(0); + mProgressBar.setMax(3); + int measured_height = listItemHeight(list_item_index); + mProgressBar.setTranslationY(measured_height); + progressbar_description.setTranslationY(measured_height + mProgressBar.getHeight()); + } + + private int getProviderIndex(String id) { + int index = 0; + Iterator providers_iterator = ProviderListContent.ITEMS.iterator(); + while(providers_iterator.hasNext()) { + ProviderItem provider = providers_iterator.next(); + if(provider.name().equalsIgnoreCase(id)) { + break; + } else index++; + } + return index; + } + + private int listItemHeight(int list_item_index) { + ListView provider_list_view = (ListView)findViewById(android.R.id.list); + ListAdapter provider_list_adapter = provider_list_view.getAdapter(); + View listItem = provider_list_adapter.getView(0, null, provider_list_view); + listItem.setLayoutParams(new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT)); + WindowManager wm = (WindowManager) getApplicationContext() + .getSystemService(Context.WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + int screenWidth = display.getWidth(); // deprecated + + int listViewWidth = screenWidth - 10 - 10; + int widthSpec = MeasureSpec.makeMeasureSpec(listViewWidth, + MeasureSpec.AT_MOST); + listItem.measure(widthSpec, 0); + + return listItem.getMeasuredHeight(); +} + + /** + * Loads providers data from url file contained in the project + * @return true if the file was read correctly + */ + private boolean loadPreseededProviders() { + boolean loaded_preseeded_providers = false; + AssetManager asset_manager = getAssets(); + String[] urls_filepaths = null; + try { + String url_files_folder = "urls"; + //TODO Put that folder in a better place (also inside the "for") + urls_filepaths = asset_manager.list(url_files_folder); + String provider_name = ""; + for(String url_filepath : urls_filepaths) + { + boolean custom = false; + provider_name = url_filepath.subSequence(0, url_filepath.indexOf(".")).toString(); + if(ProviderListContent.ITEMS.isEmpty()) //TODO I have to implement a way of checking if a provider new or is already present in that ITEMS list + ProviderListContent.addItem(new ProviderItem(provider_name, asset_manager.open(url_files_folder + "/" + url_filepath))); + loaded_preseeded_providers = true; + } + } catch (IOException e) { + loaded_preseeded_providers = false; + } + + return loaded_preseeded_providers; + } + + /** + * Asks ProviderAPI to download an anonymous (anon) VPN certificate. + */ + private void downloadAnonCert() { + Intent provider_API_command = new Intent(this, ProviderAPI.class); + + Bundle parameters = new Bundle(); + + parameters.putString(TYPE_OF_CERTIFICATE, ANON_CERTIFICATE); + + provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + + startService(provider_API_command); + } + + /** + * Open the new provider dialog + */ + public void addAndSelectNewProvider() { + FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); + Fragment previous_new_provider_dialog = getFragmentManager().findFragmentByTag(NewProviderDialog.TAG); + if (previous_new_provider_dialog != null) { + fragment_transaction.remove(previous_new_provider_dialog); + } + fragment_transaction.addToBackStack(null); + + DialogFragment newFragment = NewProviderDialog.newInstance(); + newFragment.show(fragment_transaction, NewProviderDialog.TAG); + } + + /** + * Open the new provider dialog with data + */ + public void addAndSelectNewProvider(String main_url) { + FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); + Fragment previous_new_provider_dialog = getFragmentManager().findFragmentByTag(NewProviderDialog.TAG); + if (previous_new_provider_dialog != null) { + fragment_transaction.remove(previous_new_provider_dialog); + } + + DialogFragment newFragment = NewProviderDialog.newInstance(); + Bundle data = new Bundle(); + data.putString(Provider.MAIN_URL, main_url); + newFragment.setArguments(data); + newFragment.show(fragment_transaction, NewProviderDialog.TAG); + } + + /** + * Once selected a provider, this fragment offers the user to log in, + * use it anonymously (if possible) + * or cancel his/her election pressing the back button. + * @param view + * @param reason_to_fail + */ + public void showDownloadFailedDialog(View view, String reason_to_fail) { + FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); + Fragment previous_provider_details_dialog = getFragmentManager().findFragmentByTag(DownloadFailedDialog.TAG); + if (previous_provider_details_dialog != null) { + fragment_transaction.remove(previous_provider_details_dialog); + } + fragment_transaction.addToBackStack(null); + + DialogFragment newFragment = DownloadFailedDialog.newInstance(reason_to_fail); + newFragment.show(fragment_transaction, DownloadFailedDialog.TAG); + } + + /** + * Once selected a provider, this fragment offers the user to log in, + * use it anonymously (if possible) + * or cancel his/her election pressing the back button. + * @param view + */ + public void showProviderDetails(View view) { + if(setting_up_provider) { + FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); + Fragment previous_provider_details_dialog = getFragmentManager().findFragmentByTag(ProviderDetailFragment.TAG); + if (previous_provider_details_dialog != null) { + fragment_transaction.remove(previous_provider_details_dialog); + } + fragment_transaction.addToBackStack(null); + + DialogFragment newFragment = ProviderDetailFragment.newInstance(); + newFragment.show(fragment_transaction, ProviderDetailFragment.TAG); + } + } + + public void showAndSelectProvider(String provider_main_url) { + if(getId(provider_main_url).isEmpty()) + showProvider(provider_main_url); + autoSelectProvider(provider_main_url); + } + + private void showProvider(final String provider_main_url) { + String provider_name = provider_main_url.replaceFirst("http[s]?://", "").replaceFirst("\\/", "_"); + ProviderItem added_provider = new ProviderItem(provider_name, provider_main_url); + provider_list_fragment.addItem(added_provider); + } + + private void autoSelectProvider(String provider_main_url) { + onItemSelected(getId(provider_main_url)); + } + + /** + * Asks ProviderAPI to download a new provider.json file + * @param provider_name + * @param provider_main_url + */ + public void setUpProvider(String provider_main_url) { + Intent provider_API_command = new Intent(this, ProviderAPI.class); + Bundle parameters = new Bundle(); + parameters.putString(Provider.MAIN_URL, provider_main_url); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + + startService(provider_API_command); + setting_up_provider = true; + } + + public void retrySetUpProvider() { + cancelSettingUpProvider(); + if(!ProviderAPI.caCertDownloaded()) { + addAndSelectNewProvider(ProviderAPI.lastProviderMainUrl()); + } else { + Intent provider_API_command = new Intent(this, ProviderAPI.class); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + + startService(provider_API_command); + } + } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.configuration_wizard_activity, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + switch (item.getItemId()){ + case R.id.about_leap: + startActivityForResult(new Intent(this, AboutActivity.class), 0); + return true; + case R.id.new_provider: + addAndSelectNewProvider(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + public void showAllProviders() { + provider_list_fragment = (ProviderListFragment) getFragmentManager().findFragmentByTag(ProviderListFragment.TAG); + if(provider_list_fragment != null) + provider_list_fragment.unhideAll(); + } + + public void cancelSettingUpProvider() { + provider_list_fragment = (ProviderListFragment) getFragmentManager().findFragmentByTag(ProviderListFragment.TAG); + if(provider_list_fragment != null) { + provider_list_fragment.removeLastItem(); + } + preferences.edit().remove(Provider.KEY).remove(EIP.ALLOWED_ANON).remove(EIP.KEY).commit(); + } + + @Override + public void login() { + Intent ask_login = new Intent(); + ask_login.putExtra(LogInDialog.VERB, LogInDialog.VERB); + setResult(RESULT_OK, ask_login); + setting_up_provider = false; + finish(); + } + + @Override + public void use_anonymously() { + setResult(RESULT_OK); + setting_up_provider = false; + finish(); + } + + public class ProviderAPIBroadcastReceiver_Update extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + int update = intent.getIntExtra(ProviderAPI.CURRENT_PROGRESS, 0); + mProgressBar.setProgress(update); + } + } +} diff --git a/app/src/release/java/se/leap/bitmaskclient/NewProviderDialog.java b/app/src/release/java/se/leap/bitmaskclient/NewProviderDialog.java new file mode 100644 index 00000000..7ed1940e --- /dev/null +++ b/app/src/release/java/se/leap/bitmaskclient/NewProviderDialog.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; +import se.leap.bitmaskclient.R; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Toast; + +/** + * Implements the new custom provider dialog. + * + * @author parmegv + * + */ +public class NewProviderDialog extends DialogFragment { + + final public static String TAG = "newProviderDialog"; + + public interface NewProviderDialogInterface { + public void showAndSelectProvider(String url_provider); + } + + NewProviderDialogInterface interface_with_ConfigurationWizard; + + /** + * @return a new instance of this DialogFragment. + */ + public static DialogFragment newInstance() { + NewProviderDialog dialog_fragment = new NewProviderDialog(); + return dialog_fragment; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + interface_with_ConfigurationWizard = (NewProviderDialogInterface) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement NoticeDialogListener"); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + LayoutInflater inflater = getActivity().getLayoutInflater(); + View new_provider_dialog_view = inflater.inflate(R.layout.new_provider_dialog, null); + final EditText url_input_field = (EditText)new_provider_dialog_view.findViewById(R.id.new_provider_url); + if(getArguments() != null && getArguments().containsKey(Provider.MAIN_URL)) { + url_input_field.setText(getArguments().getString(Provider.MAIN_URL)); + } + + builder.setView(new_provider_dialog_view) + .setMessage(R.string.introduce_new_provider) + .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + String entered_url = url_input_field.getText().toString().trim(); + if(!entered_url.startsWith("https://")) { + if (entered_url.startsWith("http://")){ + entered_url = entered_url.substring("http://".length()); + } + entered_url = "https://".concat(entered_url); + } + if(validURL(entered_url)) { + interface_with_ConfigurationWizard.showAndSelectProvider(entered_url); + Toast.makeText(getActivity().getApplicationContext(), R.string.valid_url_entered, Toast.LENGTH_LONG).show(); + } else { + url_input_field.setText(""); + Toast.makeText(getActivity().getApplicationContext(), R.string.not_valid_url_entered, Toast.LENGTH_LONG).show();; + } + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + // Create the AlertDialog object and return it + return builder.create(); + } + + /** + * Checks if the entered url is valid or not. + * @param entered_url + * @return true if it's not empty nor contains only the protocol. + */ + boolean validURL(String entered_url) { + //return !entered_url.isEmpty() && entered_url.matches("http[s]?://.+") && !entered_url.replaceFirst("http[s]?://", "").isEmpty(); + return android.util.Patterns.WEB_URL.matcher(entered_url).matches(); + } +} diff --git a/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java new file mode 100644 index 00000000..39ce1146 --- /dev/null +++ b/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java @@ -0,0 +1,860 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import java.io.DataOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Scanner; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import org.apache.http.client.ClientProtocolException; +import org.jboss.security.srp.SRPParameters; +import org.json.JSONException; +import org.json.JSONObject; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; +import android.app.IntentService; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.ResultReceiver; +import android.util.Base64; +import android.util.Log; + + +/** + * Implements HTTP api methods used to manage communications with the provider server. + * + * It's an IntentService because it downloads data from the Internet, so it operates in the background. + * + * @author parmegv + * @author MeanderingCode + * + */ +public class ProviderAPI extends IntentService { + + private Handler mHandler; + + final public static String + SET_UP_PROVIDER = "setUpProvider", + DOWNLOAD_NEW_PROVIDER_DOTJSON = "downloadNewProviderDotJSON", + SRP_REGISTER = "srpRegister", + SRP_AUTH = "srpAuth", + LOG_OUT = "logOut", + DOWNLOAD_CERTIFICATE = "downloadUserAuthedCertificate", + PARAMETERS = "parameters", + RESULT_KEY = "result", + RECEIVER_KEY = "receiver", + SESSION_ID_COOKIE_KEY = "session_id_cookie_key", + SESSION_ID_KEY = "session_id", + ERRORS = "errors", + UPDATE_PROGRESSBAR = "update_progressbar", + CURRENT_PROGRESS = "current_progress", + TAG = "provider_api_tag" + ; + + final public static int + CUSTOM_PROVIDER_ADDED = 0, + SRP_AUTHENTICATION_SUCCESSFUL = 3, + SRP_AUTHENTICATION_FAILED = 4, + SRP_REGISTRATION_SUCCESSFUL = 5, + SRP_REGISTRATION_FAILED = 6, + LOGOUT_SUCCESSFUL = 7, + LOGOUT_FAILED = 8, + CORRECTLY_DOWNLOADED_CERTIFICATE = 9, + INCORRECTLY_DOWNLOADED_CERTIFICATE = 10, + PROVIDER_OK = 11, + PROVIDER_NOK = 12, + CORRECTLY_DOWNLOADED_ANON_CERTIFICATE = 13, + INCORRECTLY_DOWNLOADED_ANON_CERTIFICATE = 14 + ; + + private static boolean + CA_CERT_DOWNLOADED = false, + PROVIDER_JSON_DOWNLOADED = false, + EIP_SERVICE_JSON_DOWNLOADED = false + ; + + private static String last_provider_main_url; + private static boolean setting_up_provider = true; + + public static void stop() { + setting_up_provider = false; + } + + public ProviderAPI() { + super("ProviderAPI"); + Log.v("ClassName", "Provider API"); + } + + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(); + CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER) ); + } + + public static String lastProviderMainUrl() { + return last_provider_main_url; + } + + private String formatErrorMessage(final int toast_string_id) { + return "{ \"" + ERRORS + "\" : \""+getResources().getString(toast_string_id)+"\" }"; + } + + @Override + protected void onHandleIntent(Intent command) { + final ResultReceiver receiver = command.getParcelableExtra(RECEIVER_KEY); + String action = command.getAction(); + Bundle parameters = command.getBundleExtra(PARAMETERS); + setting_up_provider = true; + + if(action.equalsIgnoreCase(SET_UP_PROVIDER)) { + Bundle result = setUpProvider(parameters); + if(setting_up_provider) { + if(result.getBoolean(RESULT_KEY)) { + receiver.send(PROVIDER_OK, result); + } else { + receiver.send(PROVIDER_NOK, result); + } + } + } else if (action.equalsIgnoreCase(SRP_AUTH)) { + Bundle session_id_bundle = authenticateBySRP(parameters); + if(session_id_bundle.getBoolean(RESULT_KEY)) { + receiver.send(SRP_AUTHENTICATION_SUCCESSFUL, session_id_bundle); + } else { + receiver.send(SRP_AUTHENTICATION_FAILED, session_id_bundle); + } + } else if (action.equalsIgnoreCase(LOG_OUT)) { + if(logOut(parameters)) { + receiver.send(LOGOUT_SUCCESSFUL, Bundle.EMPTY); + } else { + receiver.send(LOGOUT_FAILED, Bundle.EMPTY); + } + } else if (action.equalsIgnoreCase(DOWNLOAD_CERTIFICATE)) { + if(getNewCert(parameters)) { + receiver.send(CORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); + } else { + receiver.send(INCORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); + } + } + } + + /** + * Starts the authentication process using SRP protocol. + * + * @param task containing: username, password and api url. + * @return a bundle with a boolean value mapped to a key named RESULT_KEY, and which is true if authentication was successful. + */ + private Bundle authenticateBySRP(Bundle task) { + Bundle session_id_bundle = new Bundle(); + int progress = 0; + + String username = (String) task.get(LogInDialog.USERNAME); + String password = (String) task.get(LogInDialog.PASSWORD); + if(validUserLoginData(username, password)) { + + String authentication_server = (String) task.get(Provider.API_URL); + + SRPParameters params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), ConfigHelper.G.toByteArray(), BigInteger.ZERO.toByteArray(), "SHA-256"); + LeapSRPSession client = new LeapSRPSession(username, password, params); + byte[] A = client.exponential(); + broadcast_progress(progress++); + try { + JSONObject saltAndB = sendAToSRPServer(authentication_server, username, new BigInteger(1, A).toString(16)); + if(saltAndB.length() > 0) { + String salt = saltAndB.getString(LeapSRPSession.SALT); + broadcast_progress(progress++); + byte[] Bbytes = new BigInteger(saltAndB.getString("B"), 16).toByteArray(); + byte[] M1 = client.response(new BigInteger(salt, 16).toByteArray(), Bbytes); + if(M1 != null) { + broadcast_progress(progress++); + JSONObject session_idAndM2 = sendM1ToSRPServer(authentication_server, username, M1); + if(session_idAndM2.has(LeapSRPSession.M2) && client.verify((byte[])session_idAndM2.get(LeapSRPSession.M2))) { + session_id_bundle.putBoolean(RESULT_KEY, true); + broadcast_progress(progress++); + } else { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_bad_user_password_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + } + } else { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(LogInDialog.USERNAME, username); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_srp_math_error_user_message)); + } + broadcast_progress(progress++); + } else { + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_bad_user_password_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + session_id_bundle.putBoolean(RESULT_KEY, false); + } + } catch (ClientProtocolException e) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_client_http_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + } catch (IOException e) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_io_exception_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + } catch (JSONException e) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_json_exception_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + } catch (NoSuchAlgorithmException e) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(getResources().getString(R.string.user_message), getResources().getString(R.string.error_no_such_algorithm_exception_user_message)); + session_id_bundle.putString(LogInDialog.USERNAME, username); + } catch (KeyManagementException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (KeyStoreException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (CertificateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } else { + if(!wellFormedPassword(password)) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putString(LogInDialog.USERNAME, username); + session_id_bundle.putBoolean(LogInDialog.PASSWORD_INVALID_LENGTH, true); + } + if(username.isEmpty()) { + session_id_bundle.putBoolean(RESULT_KEY, false); + session_id_bundle.putBoolean(LogInDialog.USERNAME_MISSING, true); + } + } + + return session_id_bundle; + } + + /** + * Sets up an intent with the progress value passed as a parameter + * and sends it as a broadcast. + * @param progress + */ + private void broadcast_progress(int progress) { + Intent intentUpdate = new Intent(); + intentUpdate.setAction(UPDATE_PROGRESSBAR); + intentUpdate.addCategory(Intent.CATEGORY_DEFAULT); + intentUpdate.putExtra(CURRENT_PROGRESS, progress); + sendBroadcast(intentUpdate); + } + + /** + * Validates parameters entered by the user to log in + * @param entered_username + * @param entered_password + * @return true if both parameters are present and the entered password length is greater or equal to eight (8). + */ + private boolean validUserLoginData(String entered_username, String entered_password) { + return !(entered_username.isEmpty()) && wellFormedPassword(entered_password); + } + + /** + * Validates a password + * @param entered_password + * @return true if the entered password length is greater or equal to eight (8). + */ + private boolean wellFormedPassword(String entered_password) { + return entered_password.length() >= 8; + } + + /** + * Sends an HTTP POST request to the authentication server with the SRP Parameter A. + * @param server_url + * @param username + * @param clientA First SRP parameter sent + * @return response from authentication server + * @throws ClientProtocolException + * @throws IOException + * @throws JSONException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws KeyManagementException + */ + private JSONObject sendAToSRPServer(String server_url, String username, String clientA) throws ClientProtocolException, IOException, JSONException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException { + Map parameters = new HashMap(); + parameters.put("login", username); + parameters.put("A", clientA); + return sendToServer(server_url + "/sessions.json", "POST", parameters); + + /*HttpPost post = new HttpPost(server_url + "/sessions.json" + "?" + "login=" + username + "&&" + "A=" + clientA); + return sendToServer(post);*/ + } + + /** + * Sends an HTTP PUT request to the authentication server with the SRP Parameter M1 (or simply M). + * @param server_url + * @param username + * @param m1 Second SRP parameter sent + * @return response from authentication server + * @throws ClientProtocolException + * @throws IOException + * @throws JSONException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws KeyManagementException + */ + private JSONObject sendM1ToSRPServer(String server_url, String username, byte[] m1) throws ClientProtocolException, IOException, JSONException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException { + Map parameters = new HashMap(); + parameters.put("client_auth", new BigInteger(1, ConfigHelper.trim(m1)).toString(16)); + + //HttpPut put = new HttpPut(server_url + "/sessions/" + username +".json" + "?" + "client_auth" + "=" + new BigInteger(1, ConfigHelper.trim(m1)).toString(16)); + JSONObject json_response = sendToServer(server_url + "/sessions/" + username +".json", "PUT", parameters); + + JSONObject session_idAndM2 = new JSONObject(); + if(json_response.length() > 0) { + byte[] M2_not_trimmed = new BigInteger(json_response.getString(LeapSRPSession.M2), 16).toByteArray(); + /*Cookie session_id_cookie = LeapHttpClient.getInstance(getApplicationContext()).getCookieStore().getCookies().get(0); + session_idAndM2.put(ConfigHelper.SESSION_ID_COOKIE_KEY, session_id_cookie.getName()); + session_idAndM2.put(ConfigHelper.SESSION_ID_KEY, session_id_cookie.getValue());*/ + session_idAndM2.put(LeapSRPSession.M2, ConfigHelper.trim(M2_not_trimmed)); + CookieHandler.setDefault(null); // we don't need cookies anymore + String token = json_response.getString(LeapSRPSession.TOKEN); + LeapSRPSession.setToken(token); + } + return session_idAndM2; + } + + /** + * Executes an HTTP request expecting a JSON response. + * @param url + * @param request_method + * @param parameters + * @return response from authentication server + * @throws IOException + * @throws JSONException + * @throws MalformedURLException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + * @throws KeyManagementException + */ + private JSONObject sendToServer(String url, String request_method, Map parameters) throws JSONException, MalformedURLException, IOException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException { + JSONObject json_response; + InputStream is = null; + HttpsURLConnection urlConnection = (HttpsURLConnection)new URL(url).openConnection(); + urlConnection.setRequestMethod(request_method); + urlConnection.setChunkedStreamingMode(0); + urlConnection.setSSLSocketFactory(getProviderSSLSocketFactory()); + try { + + DataOutputStream writer = new DataOutputStream(urlConnection.getOutputStream()); + writer.writeBytes(formatHttpParameters(parameters)); + writer.close(); + + is = urlConnection.getInputStream(); + String plain_response = new Scanner(is).useDelimiter("\\A").next(); + json_response = new JSONObject(plain_response); + } finally { + InputStream error_stream = urlConnection.getErrorStream(); + if(error_stream != null) { + String error_response = new Scanner(error_stream).useDelimiter("\\A").next(); + urlConnection.disconnect(); + Log.d("Error", error_response); + json_response = new JSONObject(error_response); + if(!json_response.isNull(ERRORS) || json_response.has(ERRORS)) { + return new JSONObject(); + } + } + } + + return json_response; + } + + private String formatHttpParameters(Map parameters) throws UnsupportedEncodingException { + StringBuilder result = new StringBuilder(); + boolean first = true; + + Iterator parameter_iterator = parameters.keySet().iterator(); + while(parameter_iterator.hasNext()) { + if(first) + first = false; + else + result.append("&&"); + + String key = parameter_iterator.next(); + String value = parameters.get(key); + + result.append(URLEncoder.encode(key, "UTF-8")); + result.append("="); + result.append(URLEncoder.encode(value, "UTF-8")); + } + + return result.toString(); + } + + + + + /** + * Downloads a provider.json from a given URL, adding a new provider using the given name. + * @param task containing a boolean meaning if the provider is custom or not, another boolean meaning if the user completely trusts this provider, the provider name and its provider.json url. + * @return a bundle with a boolean value mapped to a key named RESULT_KEY, and which is true if the update was successful. + */ + private Bundle setUpProvider(Bundle task) { + int progress = 0; + Bundle current_download = new Bundle(); + + if(task != null && task.containsKey(Provider.MAIN_URL)) { + last_provider_main_url = task.getString(Provider.MAIN_URL); + CA_CERT_DOWNLOADED = PROVIDER_JSON_DOWNLOADED = EIP_SERVICE_JSON_DOWNLOADED = false; + } + + if(!CA_CERT_DOWNLOADED) + current_download = downloadCACert(last_provider_main_url); + if(CA_CERT_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) { + broadcast_progress(progress++); + CA_CERT_DOWNLOADED = true; + if(!PROVIDER_JSON_DOWNLOADED) + current_download = getAndSetProviderJson(last_provider_main_url); + if(PROVIDER_JSON_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) { + broadcast_progress(progress++); + PROVIDER_JSON_DOWNLOADED = true; + current_download = getAndSetEipServiceJson(); + if(current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY)) { + broadcast_progress(progress++); + EIP_SERVICE_JSON_DOWNLOADED = true; + } + } + } + + return current_download; + } + + private Bundle downloadCACert(String provider_main_url) { + Bundle result = new Bundle(); + String cert_string = downloadWithCommercialCA(provider_main_url + "/ca.crt"); + + if(validCertificate(cert_string) && setting_up_provider) { + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(Provider.CA_CERT, cert_string).commit(); + result.putBoolean(RESULT_KEY, true); + } else { + String reason_to_fail = pickErrorMessage(cert_string); + result.putString(ERRORS, reason_to_fail); + result.putBoolean(RESULT_KEY, false); + } + + return result; + } + + + public static boolean caCertDownloaded() { + return CA_CERT_DOWNLOADED; + } + + private boolean validCertificate(String cert_string) { + boolean result = false; + if(!ConfigHelper.checkErroneousDownload(cert_string)) { + X509Certificate certCert = ConfigHelper.parseX509CertificateFromString(cert_string); + try { + Base64.encodeToString( certCert.getEncoded(), Base64.DEFAULT); + result = true; + } catch (CertificateEncodingException e) { + Log.d(TAG, e.getLocalizedMessage()); + } + } + + return result; + } + + private Bundle getAndSetProviderJson(String provider_main_url) { + Bundle result = new Bundle(); + + if(setting_up_provider) { + String provider_dot_json_string = downloadWithProviderCA(provider_main_url + "/provider.json"); + + try { + JSONObject provider_json = new JSONObject(provider_dot_json_string); + String name = provider_json.getString(Provider.NAME); + //TODO setProviderName(name); + + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(Provider.KEY, provider_json.toString()).commit(); + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(EIP.ALLOWED_ANON, provider_json.getJSONObject(Provider.SERVICE).getBoolean(EIP.ALLOWED_ANON)).commit(); + + result.putBoolean(RESULT_KEY, true); + } catch (JSONException e) { + //TODO Error message should be contained in that provider_dot_json_string + String reason_to_fail = pickErrorMessage(provider_dot_json_string); + result.putString(ERRORS, reason_to_fail); + result.putBoolean(RESULT_KEY, false); + } + } + return result; + } + + + + public static boolean providerJsonDownloaded() { + return PROVIDER_JSON_DOWNLOADED; + } + + private Bundle getAndSetEipServiceJson() { + Bundle result = new Bundle(); + String eip_service_json_string = ""; + if(setting_up_provider) { + try { + JSONObject provider_json = new JSONObject(getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.KEY, "")); + String eip_service_url = provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION) + "/" + EIP.SERVICE_API_PATH; + eip_service_json_string = downloadWithProviderCA(eip_service_url); + JSONObject eip_service_json = new JSONObject(eip_service_json_string); + eip_service_json.getInt(Provider.API_RETURN_SERIAL); + + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(EIP.KEY, eip_service_json.toString()).commit(); + + result.putBoolean(RESULT_KEY, true); + } catch (JSONException e) { + String reason_to_fail = pickErrorMessage(eip_service_json_string); + result.putString(ERRORS, reason_to_fail); + result.putBoolean(RESULT_KEY, false); + } + } + return result; + } + + public static boolean eipServiceDownloaded() { + return EIP_SERVICE_JSON_DOWNLOADED; + } + + /** + * Interprets the error message as a JSON object and extract the "errors" keyword pair. + * If the error message is not a JSON object, then it is returned untouched. + * @param string_json_error_message + * @return final error message + */ + private String pickErrorMessage(String string_json_error_message) { + String error_message = ""; + try { + JSONObject json_error_message = new JSONObject(string_json_error_message); + error_message = json_error_message.getString(ERRORS); + } catch (JSONException e) { + // TODO Auto-generated catch block + error_message = string_json_error_message; + } + + return error_message; + } + + /** + * Tries to download the contents of the provided url using commercially validated CA certificate from chosen provider. + * + * @param string_url + * @return + */ + private String downloadWithCommercialCA(String string_url) { + + String json_file_content = ""; + + URL provider_url = null; + int seconds_of_timeout = 1; + try { + provider_url = new URL(string_url); + URLConnection url_connection = provider_url.openConnection(); + url_connection.setConnectTimeout(seconds_of_timeout*1000); + if(!LeapSRPSession.getToken().isEmpty()) + url_connection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token = " + LeapSRPSession.getToken()); + json_file_content = new Scanner(url_connection.getInputStream()).useDelimiter("\\A").next(); + } catch (MalformedURLException e) { + json_file_content = formatErrorMessage(R.string.malformed_url); + } catch(SocketTimeoutException e) { + json_file_content = formatErrorMessage(R.string.server_unreachable_message); + } catch (IOException e) { + if(provider_url != null) { + json_file_content = downloadWithProviderCA(string_url); + } else { + json_file_content = formatErrorMessage(R.string.certificate_error); + } + } catch (Exception e) { + if(provider_url != null) { + json_file_content = downloadWithProviderCA(string_url); + } + } + + return json_file_content; + } + + /** + * Tries to download the contents of the provided url using not commercially validated CA certificate from chosen provider. + * @param url as a string + * @return an empty string if it fails, the url content if not. + */ + private String downloadWithProviderCA(String url_string) { + String json_file_content = ""; + + try { + URL url = new URL(url_string); + // Tell the URLConnection to use a SocketFactory from our SSLContext + HttpsURLConnection urlConnection = + (HttpsURLConnection)url.openConnection(); + urlConnection.setSSLSocketFactory(getProviderSSLSocketFactory()); + if(!LeapSRPSession.getToken().isEmpty()) + urlConnection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token=" + LeapSRPSession.getToken()); + json_file_content = new Scanner(urlConnection.getInputStream()).useDelimiter("\\A").next(); + } catch (CertificateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (UnknownHostException e) { + json_file_content = formatErrorMessage(R.string.server_unreachable_message); + } catch (IOException e) { + // The downloaded certificate doesn't validate our https connection. + json_file_content = formatErrorMessage(R.string.certificate_error); + } catch (KeyStoreException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (KeyManagementException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return json_file_content; + } + + private javax.net.ssl.SSLSocketFactory getProviderSSLSocketFactory() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException { + String provider_cert_string = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.CA_CERT,""); + + java.security.cert.Certificate provider_certificate = ConfigHelper.parseX509CertificateFromString(provider_cert_string); + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("provider_ca_certificate", provider_certificate); + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + + // Create an SSLContext that uses our TrustManager + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + + return context.getSocketFactory(); + } + + /** + * Downloads the string that's in the url with any certificate. + */ + private String downloadWithoutCA(String url_string) { + String string = ""; + try { + + HostnameVerifier hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + class DefaultTrustManager implements X509TrustManager { + + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} + + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + } + + SSLContext context = SSLContext.getInstance("TLS"); + context.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom()); + + URL url = new URL(url_string); + HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); + urlConnection.setSSLSocketFactory(context.getSocketFactory()); + urlConnection.setHostnameVerifier(hostnameVerifier); + string = new Scanner(urlConnection.getInputStream()).useDelimiter("\\A").next(); + System.out.println("String ignoring certificate = " + string); + } catch (FileNotFoundException e) { + e.printStackTrace(); + string = formatErrorMessage(R.string.server_unreachable_message); + } catch (IOException e) { + // The downloaded certificate doesn't validate our https connection. + e.printStackTrace(); + string = formatErrorMessage(R.string.certificate_error); + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (KeyManagementException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return string; + } + + /** + * Logs out from the api url retrieved from the task. + * @param task containing api url from which the user will log out + * @return true if there were no exceptions + */ + private boolean logOut(Bundle task) { + try { + String delete_url = task.getString(Provider.API_URL) + "/logout"; + int progress = 0; + + HttpsURLConnection urlConnection = (HttpsURLConnection)new URL(delete_url).openConnection(); + urlConnection.setRequestMethod("DELETE"); + urlConnection.setSSLSocketFactory(getProviderSSLSocketFactory()); + + int responseCode = urlConnection.getResponseCode(); + broadcast_progress(progress++); + LeapSRPSession.setToken(""); + Log.d(TAG, Integer.toString(responseCode)); + } catch (ClientProtocolException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } catch (IndexOutOfBoundsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } catch (KeyManagementException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } 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(); + } + return true; + } + + /** + * Downloads a new OpenVPN certificate, attaching authenticated cookie for authenticated certificate. + * + * @param task containing the type of the certificate to be downloaded + * @return true if certificate was downloaded correctly, false if provider.json is not present in SharedPreferences, or if the certificate url could not be parsed as a URI, or if there was an SSL error. + */ + private boolean getNewCert(Bundle task) { + + try { + String type_of_certificate = task.getString(ConfigurationWizard.TYPE_OF_CERTIFICATE); + JSONObject provider_json = new JSONObject(getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).getString(Provider.KEY, "")); + + String provider_main_url = provider_json.getString(Provider.API_URL); + URL new_cert_string_url = new URL(provider_main_url + "/" + provider_json.getString(Provider.API_VERSION) + "/" + EIP.CERTIFICATE); + + String cert_string = downloadWithProviderCA(new_cert_string_url.toString()); + + if(!cert_string.isEmpty()) { + if(ConfigHelper.checkErroneousDownload(cert_string)) { + String reason_to_fail = provider_json.getString(ERRORS); + //result.putString(ConfigHelper.ERRORS_KEY, reason_to_fail); + //result.putBoolean(ConfigHelper.RESULT_KEY, false); + return false; + } else { + + // API returns concatenated cert & key. Split them for OpenVPN options + String certificateString = null, keyString = null; + String[] certAndKey = cert_string.split("(?<=-\n)"); + for (int i=0; i < certAndKey.length-1; i++){ + if ( certAndKey[i].contains("KEY") ) { + keyString = certAndKey[i++] + certAndKey[i]; + } + else if ( certAndKey[i].contains("CERTIFICATE") ) { + certificateString = certAndKey[i++] + certAndKey[i]; + } + } + try { + RSAPrivateKey keyCert = ConfigHelper.parseRsaKeyFromString(keyString); + keyString = Base64.encodeToString( keyCert.getEncoded(), Base64.DEFAULT ); + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(EIP.PRIVATE_KEY, "-----BEGIN RSA PRIVATE KEY-----\n"+keyString+"-----END RSA PRIVATE KEY-----").commit(); + + X509Certificate certCert = ConfigHelper.parseX509CertificateFromString(certificateString); + certificateString = Base64.encodeToString( certCert.getEncoded(), Base64.DEFAULT); + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putString(EIP.CERTIFICATE, "-----BEGIN CERTIFICATE-----\n"+certificateString+"-----END CERTIFICATE-----").commit(); + + return true; + } catch (CertificateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } + } + } else { + return false; + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } /*catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + }*/ + } +} diff --git a/app/src/release/java/se/leap/bitmaskclient/ProviderDetailFragment.java b/app/src/release/java/se/leap/bitmaskclient/ProviderDetailFragment.java new file mode 100644 index 00000000..42cdd516 --- /dev/null +++ b/app/src/release/java/se/leap/bitmaskclient/ProviderDetailFragment.java @@ -0,0 +1,115 @@ +package se.leap.bitmaskclient; + +import org.json.JSONException; +import org.json.JSONObject; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +public class ProviderDetailFragment extends DialogFragment { + + final public static String TAG = "providerDetailFragment"; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + try { + + LayoutInflater inflater = getActivity().getLayoutInflater(); + View provider_detail_view = inflater.inflate(R.layout.provider_detail_fragment, null); + + JSONObject provider_json = new JSONObject(getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, getActivity().MODE_PRIVATE).getString(Provider.KEY, "")); + + final TextView domain = (TextView)provider_detail_view.findViewById(R.id.provider_detail_domain); + domain.setText(provider_json.getString(Provider.DOMAIN)); + final TextView name = (TextView)provider_detail_view.findViewById(R.id.provider_detail_name); + name.setText(provider_json.getJSONObject(Provider.NAME).getString("en")); + final TextView description = (TextView)provider_detail_view.findViewById(R.id.provider_detail_description); + description.setText(provider_json.getJSONObject(Provider.DESCRIPTION).getString("en")); + + builder.setView(provider_detail_view); + builder.setTitle(R.string.provider_details_fragment_title); + + if(anon_allowed(provider_json)) { + builder.setPositiveButton(R.string.use_anonymously_button, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + interface_with_configuration_wizard.use_anonymously(); + } + }); + } + + if(registration_allowed(provider_json)) { + builder.setNegativeButton(R.string.login_button, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + interface_with_configuration_wizard.login(); + } + }); + } + + return builder.create(); + } catch (JSONException e) { + return null; + } + } + + private boolean anon_allowed(JSONObject provider_json) { + try { + JSONObject service_description = provider_json.getJSONObject(Provider.SERVICE); + return service_description.has(EIP.ALLOWED_ANON) && service_description.getBoolean(EIP.ALLOWED_ANON); + } catch (JSONException e) { + return false; + } + } + + private boolean registration_allowed(JSONObject provider_json) { + try { + JSONObject service_description = provider_json.getJSONObject(Provider.SERVICE); + return service_description.has(Provider.ALLOW_REGISTRATION) && service_description.getBoolean(Provider.ALLOW_REGISTRATION); + } catch (JSONException e) { + return false; + } + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + SharedPreferences.Editor editor = getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit(); + editor.remove(Provider.KEY).remove(EIP.ALLOWED_ANON).remove(EIP.KEY).commit(); + interface_with_configuration_wizard.showAllProviders(); + } + + public static DialogFragment newInstance() { + ProviderDetailFragment provider_detail_fragment = new ProviderDetailFragment(); + return provider_detail_fragment; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + interface_with_configuration_wizard = (ProviderDetailFragmentInterface) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement LogInDialogListener"); + } + } + + public interface ProviderDetailFragmentInterface { + public void login(); + public void use_anonymously(); + public void showAllProviders(); + } + + ProviderDetailFragmentInterface interface_with_configuration_wizard; +} diff --git a/app/src/release/java/se/leap/bitmaskclient/ProviderListContent.java b/app/src/release/java/se/leap/bitmaskclient/ProviderListContent.java new file mode 100644 index 00000000..230c297f --- /dev/null +++ b/app/src/release/java/se/leap/bitmaskclient/ProviderListContent.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package se.leap.bitmaskclient; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.net.URL; +import java.net.MalformedURLException; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Models the provider list shown in the ConfigurationWizard. + * + * @author parmegv + * + */ +public class ProviderListContent { + + public static List ITEMS = new ArrayList(); + + public static Map ITEM_MAP = new HashMap(); + + /** + * Adds a new provider item to the end of the items map, and to the items list. + * @param item + */ + public static void addItem(ProviderItem item) { + ITEMS.add(item); + ITEM_MAP.put(String.valueOf(ITEMS.size()), item); + } + public static void removeItem(ProviderItem item) { + ITEMS.remove(item); + ITEM_MAP.remove(item); + } + + /** + * A provider item. + */ + public static class ProviderItem { + final public static String CUSTOM = "custom"; + private String provider_main_url; + private String name; + + /** + * @param name of the provider + * @param urls_file_input_stream file input stream linking with the assets url file + * @param custom if it's a new provider entered by the user or not + */ + public ProviderItem(String name, InputStream urls_file_input_stream) { + + try { + byte[] urls_file_bytes = new byte[urls_file_input_stream.available()]; + urls_file_input_stream.read(urls_file_bytes); + String urls_file_content = new String(urls_file_bytes); + JSONObject file_contents = new JSONObject(urls_file_content); + provider_main_url = file_contents.getString(Provider.MAIN_URL); + this.name = name; + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * @param name of the provider + * @param provider_main_url used to download provider.json file of the provider + * @param provider_json already downloaded + * @param custom if it's a new provider entered by the user or not + */ + public ProviderItem(String name, String provider_main_url) { + this.name = name; + this.provider_main_url = provider_main_url; + } + + public String name() { return name; } + + public String providerMainUrl() { return provider_main_url; } + + public String domain() { + try { + return new URL(provider_main_url).getHost(); + } catch (MalformedURLException e) { + return provider_main_url.replaceFirst("http[s]?://", "").replaceFirst("/.*", ""); + } + } + } +} diff --git a/app/src/release/res/layout-xlarge/new_provider_dialog.xml b/app/src/release/res/layout-xlarge/new_provider_dialog.xml new file mode 100644 index 00000000..12616625 --- /dev/null +++ b/app/src/release/res/layout-xlarge/new_provider_dialog.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/release/res/layout/new_provider_dialog.xml b/app/src/release/res/layout/new_provider_dialog.xml new file mode 100644 index 00000000..36eb0d6a --- /dev/null +++ b/app/src/release/res/layout/new_provider_dialog.xml @@ -0,0 +1,18 @@ + + + + + + -- cgit v1.2.3 From 4a8c2f34c3bbac63e0900f86bff53d11b7831151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Wed, 9 Apr 2014 18:43:29 +0200 Subject: Bitmask loads all preseeded providers correctly. If you choose one and then tap "Switch provider" button, no duplicated providers are shown. --- .../se/leap/bitmaskclient/ConfigurationWizard.java | 70 +++++++++++++--------- .../se/leap/bitmaskclient/ProviderListContent.java | 28 +-------- .../se/leap/bitmaskclient/ConfigurationWizard.java | 70 +++++++++++++--------- .../se/leap/bitmaskclient/ProviderListContent.java | 25 -------- 4 files changed, 85 insertions(+), 108 deletions(-) (limited to 'app/src') diff --git a/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java b/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java index f0aac40b..fb63b47a 100644 --- a/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java +++ b/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java @@ -49,6 +49,7 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; @@ -84,6 +85,7 @@ implements ProviderListFragment.Callbacks, NewProviderDialogInterface, ProviderD final protected static String PROVIDER_SET = "PROVIDER SET"; final protected static String SERVICES_RETRIEVED = "SERVICES RETRIEVED"; + final protected static String ASSETS_URL_FOLDER = "urls"; public ProviderAPIResultReceiver providerAPI_result_receiver; private ProviderAPIBroadcastReceiver_Update providerAPI_broadcast_receiver_update; @@ -353,34 +355,46 @@ implements ProviderListFragment.Callbacks, NewProviderDialogInterface, ProviderD return listItem.getMeasuredHeight(); } - /** - * Loads providers data from url file contained in the project - * @return true if the file was read correctly - */ - private boolean loadPreseededProviders() { - boolean loaded_preseeded_providers = false; - AssetManager asset_manager = getAssets(); - String[] urls_filepaths = null; - try { - String url_files_folder = "urls"; - //TODO Put that folder in a better place (also inside the "for") - urls_filepaths = asset_manager.list(url_files_folder); - String provider_name = ""; - for(String url_filepath : urls_filepaths) - { - boolean custom = false; - provider_name = url_filepath.subSequence(0, url_filepath.indexOf(".")).toString(); - if(ProviderListContent.ITEMS.isEmpty()) //TODO I have to implement a way of checking if a provider new or is already present in that ITEMS list - ProviderListContent.addItem(new ProviderItem(provider_name, asset_manager.open(url_files_folder + "/" + url_filepath))); - loaded_preseeded_providers = true; - } - } catch (IOException e) { - loaded_preseeded_providers = false; - } - - return loaded_preseeded_providers; - } - + /** + * Loads providers data from url files contained in the assets folder + * @return true if the files were correctly read + */ + private boolean loadPreseededProviders() { + boolean loaded_preseeded_providers = false; + String[] urls_filepaths = null; + try { + //TODO Put that folder in a better place (also inside the "for") + urls_filepaths = getAssets().list(ASSETS_URL_FOLDER); + String provider_name = ""; + for(String url_filepath : urls_filepaths) { + provider_name = url_filepath.subSequence(0, url_filepath.lastIndexOf(".")).toString(); + String provider_main_url = extractProviderMainUrlFromAssetsFile(ASSETS_URL_FOLDER + "/" + url_filepath); + if(getId(provider_main_url).isEmpty()) + ProviderListContent.addItem(new ProviderItem(provider_name, provider_main_url)); + loaded_preseeded_providers = true; + } + } catch (IOException e) { + loaded_preseeded_providers = false; + } + + return loaded_preseeded_providers; + } + + private String extractProviderMainUrlFromAssetsFile(String filepath) { + String provider_main_url = ""; + try { + InputStream input_stream_file_contents = getAssets().open(filepath); + byte[] urls_file_bytes = new byte[input_stream_file_contents.available()]; + input_stream_file_contents.read(urls_file_bytes); + String urls_file_content = new String(urls_file_bytes); + JSONObject file_contents = new JSONObject(urls_file_content); + provider_main_url = file_contents.getString(Provider.MAIN_URL); + } catch (JSONException e) { + } catch (IOException e) { + } + return provider_main_url; + } + /** * Asks ProviderAPI to download an anonymous (anon) VPN certificate. */ diff --git a/app/src/debug/java/se/leap/bitmaskclient/ProviderListContent.java b/app/src/debug/java/se/leap/bitmaskclient/ProviderListContent.java index e1ca4f9a..8c8fb69f 100644 --- a/app/src/debug/java/se/leap/bitmaskclient/ProviderListContent.java +++ b/app/src/debug/java/se/leap/bitmaskclient/ProviderListContent.java @@ -61,36 +61,10 @@ public class ProviderListContent { final public static String DANGER_ON = "danger_on"; private String provider_main_url; private String name; - - /** - * @param name of the provider - * @param urls_file_input_stream file input stream linking with the assets url file - * @param custom if it's a new provider entered by the user or not - * @param danger_on if the user trusts completely the new provider - */ - public ProviderItem(String name, InputStream urls_file_input_stream) { - - try { - byte[] urls_file_bytes = new byte[urls_file_input_stream.available()]; - urls_file_input_stream.read(urls_file_bytes); - String urls_file_content = new String(urls_file_bytes); - JSONObject file_contents = new JSONObject(urls_file_content); - provider_main_url = file_contents.getString(Provider.MAIN_URL); - this.name = name; - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - + /** * @param name of the provider * @param provider_main_url used to download provider.json file of the provider - * @param provider_json already downloaded - * @param custom if it's a new provider entered by the user or not */ public ProviderItem(String name, String provider_main_url) { this.name = name; diff --git a/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java b/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java index 15a135d2..a6b009e3 100644 --- a/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java +++ b/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java @@ -49,6 +49,7 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; @@ -84,6 +85,7 @@ implements ProviderListFragment.Callbacks, NewProviderDialogInterface, ProviderD final protected static String PROVIDER_SET = "PROVIDER SET"; final protected static String SERVICES_RETRIEVED = "SERVICES RETRIEVED"; + final protected static String ASSETS_URL_FOLDER = "urls"; public ProviderAPIResultReceiver providerAPI_result_receiver; private ProviderAPIBroadcastReceiver_Update providerAPI_broadcast_receiver_update; @@ -350,34 +352,46 @@ implements ProviderListFragment.Callbacks, NewProviderDialogInterface, ProviderD return listItem.getMeasuredHeight(); } - /** - * Loads providers data from url file contained in the project - * @return true if the file was read correctly - */ - private boolean loadPreseededProviders() { - boolean loaded_preseeded_providers = false; - AssetManager asset_manager = getAssets(); - String[] urls_filepaths = null; - try { - String url_files_folder = "urls"; - //TODO Put that folder in a better place (also inside the "for") - urls_filepaths = asset_manager.list(url_files_folder); - String provider_name = ""; - for(String url_filepath : urls_filepaths) - { - boolean custom = false; - provider_name = url_filepath.subSequence(0, url_filepath.indexOf(".")).toString(); - if(ProviderListContent.ITEMS.isEmpty()) //TODO I have to implement a way of checking if a provider new or is already present in that ITEMS list - ProviderListContent.addItem(new ProviderItem(provider_name, asset_manager.open(url_files_folder + "/" + url_filepath))); - loaded_preseeded_providers = true; - } - } catch (IOException e) { - loaded_preseeded_providers = false; - } - - return loaded_preseeded_providers; - } - + /** + * Loads providers data from url files contained in the assets folder + * @return true if the files were correctly read + */ + private boolean loadPreseededProviders() { + boolean loaded_preseeded_providers = false; + String[] urls_filepaths = null; + try { + //TODO Put that folder in a better place (also inside the "for") + urls_filepaths = getAssets().list(ASSETS_URL_FOLDER); + String provider_name = ""; + for(String url_filepath : urls_filepaths) { + provider_name = url_filepath.subSequence(0, url_filepath.lastIndexOf(".")).toString(); + String provider_main_url = extractProviderMainUrlFromAssetsFile(ASSETS_URL_FOLDER + "/" + url_filepath); + if(getId(provider_main_url).isEmpty()) + ProviderListContent.addItem(new ProviderItem(provider_name, provider_main_url)); + loaded_preseeded_providers = true; + } + } catch (IOException e) { + loaded_preseeded_providers = false; + } + + return loaded_preseeded_providers; + } + + private String extractProviderMainUrlFromAssetsFile(String filepath) { + String provider_main_url = ""; + try { + InputStream input_stream_file_contents = getAssets().open(filepath); + byte[] urls_file_bytes = new byte[input_stream_file_contents.available()]; + input_stream_file_contents.read(urls_file_bytes); + String urls_file_content = new String(urls_file_bytes); + JSONObject file_contents = new JSONObject(urls_file_content); + provider_main_url = file_contents.getString(Provider.MAIN_URL); + } catch (JSONException e) { + } catch (IOException e) { + } + return provider_main_url; + } + /** * Asks ProviderAPI to download an anonymous (anon) VPN certificate. */ diff --git a/app/src/release/java/se/leap/bitmaskclient/ProviderListContent.java b/app/src/release/java/se/leap/bitmaskclient/ProviderListContent.java index 230c297f..a5402fd8 100644 --- a/app/src/release/java/se/leap/bitmaskclient/ProviderListContent.java +++ b/app/src/release/java/se/leap/bitmaskclient/ProviderListContent.java @@ -61,34 +61,9 @@ public class ProviderListContent { private String provider_main_url; private String name; - /** - * @param name of the provider - * @param urls_file_input_stream file input stream linking with the assets url file - * @param custom if it's a new provider entered by the user or not - */ - public ProviderItem(String name, InputStream urls_file_input_stream) { - - try { - byte[] urls_file_bytes = new byte[urls_file_input_stream.available()]; - urls_file_input_stream.read(urls_file_bytes); - String urls_file_content = new String(urls_file_bytes); - JSONObject file_contents = new JSONObject(urls_file_content); - provider_main_url = file_contents.getString(Provider.MAIN_URL); - this.name = name; - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - /** * @param name of the provider * @param provider_main_url used to download provider.json file of the provider - * @param provider_json already downloaded - * @param custom if it's a new provider entered by the user or not */ public ProviderItem(String name, String provider_main_url) { this.name = name; -- cgit v1.2.3 From cf09213cdf201362e1a6c38e125b096e99d39476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Mon, 14 Apr 2014 18:19:04 +0200 Subject: testAddNewProvider adds calyx.net The test that checked how many providers were listed was failing because this test added dev.bm.net, which was already present in the debug apk, so no new provider was added. --- .../java/se/leap/bitmaskclient/test/testConfigurationWizard.java | 2 +- .../androidTest/java/se/leap/bitmaskclient/test/testDashboard.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'app/src') diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java index 847d8cdd..8ddfbc57 100644 --- a/app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java +++ b/app/src/androidTest/java/se/leap/bitmaskclient/test/testConfigurationWizard.java @@ -46,7 +46,7 @@ public class testConfigurationWizard extends ActivityInstrumentationTestCase2 { if(!solo.waitForText(getActivity().getString(R.string.eip_state_not_connected))) fail(); - ConnectionManager.setMobileDataEnabled(false, solo.getCurrentActivity().getApplicationContext()); + /*ConnectionManager.setMobileDataEnabled(false, solo.getCurrentActivity().getApplicationContext()); solo.clickOnView(solo.getView(R.id.eipSwitch)); if(!solo.waitForText(getActivity().getString(R.string.eip_status_start_pending))) fail(); if(!solo.waitForText(getActivity().getString(R.string.state_nonetwork))) fail(); - + */ } public void testLogInAndOut() { -- cgit v1.2.3 From 94ccdbfcc441d2992f108e5fa90733b54ab763e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Mon, 14 Apr 2014 18:31:32 +0200 Subject: testSwitchProvider nullpointer fixed. --- app/src/main/java/se/leap/bitmaskclient/Dashboard.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index b388b84a..292434c7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -114,7 +114,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf View view = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0); logInDialog(view, Bundle.EMPTY); } - } else if(resultCode == RESULT_CANCELED && data.hasExtra(ACTION_QUIT)) { + } else if(resultCode == RESULT_CANCELED && (data == null || data.hasExtra(ACTION_QUIT))) { finish(); } else configErrorDialog(); -- cgit v1.2.3 From b9a190335150e458099d81b70c16462fbe9e3cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Wed, 22 Jan 2014 20:50:53 +0100 Subject: Always restore last eip status on boot. Next step: don't restore off status! --- .../main/java/se/leap/bitmaskclient/Dashboard.java | 22 ++++++++++++++++++++-- app/src/main/java/se/leap/bitmaskclient/EIP.java | 2 ++ .../se/leap/bitmaskclient/EipServiceFragment.java | 14 ++++++++++---- .../main/java/se/leap/openvpn/ProfileManager.java | 3 ++- 4 files changed, 34 insertions(+), 7 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 292434c7..27c9f949 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -57,7 +57,9 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf final public static String SHARED_PREFERENCES = "LEAPPreferences"; final public static String ACTION_QUIT = "quit"; - public static final String REQUEST_CODE = "request_code"; + public static final String REQUEST_CODE = "request_code"; + public static final String PARAMETERS = "dashboard parameters"; + public static final String START_ON_BOOT = "dashboard start on boot"; private ProgressBar mProgressBar; private TextView eipStatus; @@ -86,11 +88,17 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); +<<<<<<< HEAD:app/src/main/java/se/leap/bitmaskclient/Dashboard.java authed_eip = preferences.getBoolean(EIP.AUTHED_EIP, false); if (preferences.getString(Provider.KEY, "").isEmpty()) startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); +======= + authed_eip = ConfigHelper.getBoolFromSharedPref(EIP.AUTHED_EIP); + if (ConfigHelper.getStringFromSharedPref(Provider.KEY).isEmpty()) + startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); +>>>>>>> Always restore last eip status on boot.:src/se/leap/bitmaskclient/Dashboard.java else - buildDashboard(); + buildDashboard(); } @Override @@ -166,8 +174,18 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf FragmentManager fragMan = getFragmentManager(); if ( provider.hasEIP()){ +<<<<<<< HEAD:app/src/main/java/se/leap/bitmaskclient/Dashboard.java EipServiceFragment eipFragment = new EipServiceFragment(); fragMan.beginTransaction().replace(R.id.servicesCollection, eipFragment, EipServiceFragment.TAG).commit(); +======= + EipServiceFragment eipFragment = new EipServiceFragment(); + if (getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE).getBoolean(Dashboard.START_ON_BOOT, false)) { + Bundle arguments = new Bundle(); + arguments.putBoolean(EipServiceFragment.START_ON_BOOT, true); + eipFragment.setArguments(arguments); + } + fragMan.beginTransaction().replace(R.id.servicesCollection, eipFragment, TAG_EIP_FRAGMENT).commit(); +>>>>>>> Always restore last eip status on boot.:src/se/leap/bitmaskclient/Dashboard.java } } diff --git a/app/src/main/java/se/leap/bitmaskclient/EIP.java b/app/src/main/java/se/leap/bitmaskclient/EIP.java index e773e3b9..49243ffc 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/EIP.java @@ -245,6 +245,7 @@ public final class EIP extends IntentService { intent.putExtra(RECEIVER_TAG, mReceiver); startActivity(intent); mPending = ACTION_START_EIP; + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(Dashboard.START_ON_BOOT, true).commit(); } /** @@ -262,6 +263,7 @@ public final class EIP extends IntentService { resultData.putString(REQUEST_TAG, ACTION_STOP_EIP); mReceiver.send(Activity.RESULT_OK, resultData); } + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(Dashboard.START_ON_BOOT, false).commit(); } /** diff --git a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java index b4cb541a..ff204efc 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java @@ -27,6 +27,7 @@ import android.widget.TextView; public class EipServiceFragment extends Fragment implements StateListener, OnCheckedChangeListener { protected static final String IS_EIP_PENDING = "is_eip_pending"; + public static final String START_ON_BOOT = "start on boot"; private View eipFragment; private static Switch eipSwitch; @@ -73,6 +74,8 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe }); eipSwitch.setOnCheckedChangeListener(this); + if(getArguments() != null && getArguments().containsKey(START_ON_BOOT) && getArguments().getBoolean(START_ON_BOOT)) + startEipFromScratch(); return eipFragment; } @@ -122,10 +125,7 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe String certificate = getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).getString(EIP.CERTIFICATE, ""); if(allowed_anon || !certificate.isEmpty()) { if (isChecked){ - mEipStartPending = true; - eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); - ((TextView) eipFragment.findViewById(R.id.eipStatus)).setText(R.string.eip_status_start_pending); - eipCommand(EIP.ACTION_START_EIP); + startEipFromScratch(); } else { if (mEipStartPending){ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity()); @@ -168,6 +168,12 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe } + public void startEipFromScratch() { + mEipStartPending = true; + eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); + ((TextView) eipFragment.findViewById(R.id.eipStatus)).setText(R.string.eip_status_start_pending); + eipCommand(EIP.ACTION_START_EIP); + } /** * Send a command to EIP diff --git a/app/src/main/java/se/leap/openvpn/ProfileManager.java b/app/src/main/java/se/leap/openvpn/ProfileManager.java index b9eb3ab6..07a4087a 100644 --- a/app/src/main/java/se/leap/openvpn/ProfileManager.java +++ b/app/src/main/java/se/leap/openvpn/ProfileManager.java @@ -71,7 +71,8 @@ public class ProfileManager { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); Editor prefsedit = prefs.edit(); - prefsedit.putString(ONBOOTPROFILE, connectedrofile.getUUIDString()); + //prefsedit.putString(ONBOOTPROFILE, connectedrofile.getUUIDString()); + prefsedit.putString(ONBOOTPROFILE, VpnProfile.EXTRA_PROFILEUUID); prefsedit.apply(); mLastConnectedVpn=connectedrofile; -- cgit v1.2.3 From 0a4162684fe5874f20a4d1e79eae611b93f72a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Wed, 22 Jan 2014 21:30:13 +0100 Subject: Bitmask doesn't start if last EIP status was off. --- app/src/main/java/se/leap/bitmaskclient/EIP.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/EIP.java b/app/src/main/java/se/leap/bitmaskclient/EIP.java index 49243ffc..31a06d96 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/EIP.java @@ -263,7 +263,7 @@ public final class EIP extends IntentService { resultData.putString(REQUEST_TAG, ACTION_STOP_EIP); mReceiver.send(Activity.RESULT_OK, resultData); } - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(Dashboard.START_ON_BOOT, false).commit(); + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().remove(Dashboard.START_ON_BOOT).commit(); } /** -- cgit v1.2.3 From 365a517ab7571bce056812253cdbb410f3aa8e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Mon, 27 Jan 2014 18:39:13 +0100 Subject: Launcher and notification reuse existing Activity. Notifications get mad, we have to fix that. --- .../se/leap/bitmaskclient/ConfigurationWizard.java | 2 +- .../main/java/se/leap/bitmaskclient/Dashboard.java | 39 ++++++++++++---------- app/src/main/java/se/leap/bitmaskclient/EIP.java | 11 +++--- .../se/leap/bitmaskclient/EipServiceFragment.java | 11 ++++-- .../main/java/se/leap/openvpn/OpenVpnService.java | 3 +- 5 files changed, 39 insertions(+), 27 deletions(-) (limited to 'app/src') diff --git a/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java b/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java index fb63b47a..cf013f3a 100644 --- a/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java +++ b/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java @@ -81,7 +81,7 @@ implements ProviderListFragment.Callbacks, NewProviderDialogInterface, ProviderD final public static String TAG = "se.leap.bitmaskclient.ConfigurationWizard"; final public static String TYPE_OF_CERTIFICATE = "type_of_certificate"; final public static String ANON_CERTIFICATE = "anon_certificate"; - final public static String AUTHED_CERTIFICATE = "authed_certificate"; + final public static String AUTHED_CERTIFICATE = "authed_certificate"; final protected static String PROVIDER_SET = "PROVIDER SET"; final protected static String SERVICES_RETRIEVED = "SERVICES RETRIEVED"; diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 27c9f949..b95f319f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -60,6 +60,8 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf public static final String REQUEST_CODE = "request_code"; public static final String PARAMETERS = "dashboard parameters"; public static final String START_ON_BOOT = "dashboard start on boot"; + final public static String ON_BOOT = "dashboard on boot"; + private ProgressBar mProgressBar; private TextView eipStatus; @@ -88,24 +90,26 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); -<<<<<<< HEAD:app/src/main/java/se/leap/bitmaskclient/Dashboard.java authed_eip = preferences.getBoolean(EIP.AUTHED_EIP, false); if (preferences.getString(Provider.KEY, "").isEmpty()) startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); -======= - authed_eip = ConfigHelper.getBoolFromSharedPref(EIP.AUTHED_EIP); - if (ConfigHelper.getStringFromSharedPref(Provider.KEY).isEmpty()) - startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); ->>>>>>> Always restore last eip status on boot.:src/se/leap/bitmaskclient/Dashboard.java else - buildDashboard(); + buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false)); } @Override protected void onDestroy() { super.onDestroy(); } - + + protected void onPause() { + super.onPause(); + Log.d("Dashboard", "eip_service_fragment.saveEipStatus()"); + EipServiceFragment eip_service_fragment = (EipServiceFragment) getFragmentManager().findFragmentByTag(TAG_EIP_FRAGMENT); + if(eip_service_fragment != null) + eip_service_fragment.saveEipStatus(); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data){ if ( requestCode == CONFIGURE_LEAP || requestCode == SWITCH_PROVIDER) { @@ -160,7 +164,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf * Inflates permanent UI elements of the View and contains logic for what * service dependent UI elements to include. */ - private void buildDashboard() { + private void buildDashboard(boolean hide_and_turn_on_eip) { provider = Provider.getInstance(); provider.init( this ); @@ -174,18 +178,19 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf FragmentManager fragMan = getFragmentManager(); if ( provider.hasEIP()){ -<<<<<<< HEAD:app/src/main/java/se/leap/bitmaskclient/Dashboard.java EipServiceFragment eipFragment = new EipServiceFragment(); - fragMan.beginTransaction().replace(R.id.servicesCollection, eipFragment, EipServiceFragment.TAG).commit(); -======= EipServiceFragment eipFragment = new EipServiceFragment(); - if (getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE).getBoolean(Dashboard.START_ON_BOOT, false)) { + if (hide_and_turn_on_eip) { + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().remove(Dashboard.START_ON_BOOT).commit(); Bundle arguments = new Bundle(); arguments.putBoolean(EipServiceFragment.START_ON_BOOT, true); eipFragment.setArguments(arguments); } - fragMan.beginTransaction().replace(R.id.servicesCollection, eipFragment, TAG_EIP_FRAGMENT).commit(); ->>>>>>> Always restore last eip status on boot.:src/se/leap/bitmaskclient/Dashboard.java + fragMan.beginTransaction().replace(R.id.servicesCollection, eipFragment, EipServiceFragment.TAG).commit(); + + if (hide_and_turn_on_eip) { + onBackPressed(); + } } } @@ -482,9 +487,9 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? Intent eip_intent = new Intent(this, EIP.class); eip_intent.setAction(EIP.ACTION_STOP_EIP); - // eip_intent.putExtra(EIP.RECEIVER_TAG, eip_receiver); + // eip_intent.putExtra(EIP.RECEIVER_TAG, eip_receiver);fi startService(eip_intent); - + } private void eipStart(){ diff --git a/app/src/main/java/se/leap/bitmaskclient/EIP.java b/app/src/main/java/se/leap/bitmaskclient/EIP.java index 31a06d96..68688b90 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/EIP.java @@ -108,11 +108,12 @@ public final class EIP extends IntentService { @Override public void onDestroy() { - unbindService(mVpnServiceConn); - mBound = false; - - super.onDestroy(); + unbindService(mVpnServiceConn); + mBound = false; + + super.onDestroy(); } + @Override protected void onHandleIntent(Intent intent) { @@ -245,7 +246,6 @@ public final class EIP extends IntentService { intent.putExtra(RECEIVER_TAG, mReceiver); startActivity(intent); mPending = ACTION_START_EIP; - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().putBoolean(Dashboard.START_ON_BOOT, true).commit(); } /** @@ -263,7 +263,6 @@ public final class EIP extends IntentService { resultData.putString(REQUEST_TAG, ACTION_STOP_EIP); mReceiver.send(Activity.RESULT_OK, resultData); } - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().remove(Dashboard.START_ON_BOOT).commit(); } /** diff --git a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java index ff204efc..4cc6be4f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java @@ -111,13 +111,20 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe OpenVPN.removeStateListener(this); } - + @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(IS_EIP_PENDING, mEipStartPending); } - + + protected void saveEipStatus() { + boolean eip_is_on = false; + if(eipSwitch.isChecked()) { + eip_is_on = true; + } + getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit().putBoolean(Dashboard.START_ON_BOOT, eip_is_on).commit(); + } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (buttonView.equals(eipSwitch) && !eipAutoSwitched){ diff --git a/app/src/main/java/se/leap/openvpn/OpenVpnService.java b/app/src/main/java/se/leap/openvpn/OpenVpnService.java index b5c9c798..11071802 100644 --- a/app/src/main/java/se/leap/openvpn/OpenVpnService.java +++ b/app/src/main/java/se/leap/openvpn/OpenVpnService.java @@ -278,10 +278,11 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac @Override public void onDestroy() { - if (mProcessThread != null) { + if (mProcessThread != null) { mSocketManager.managmentCommand("signal SIGINT\n"); mProcessThread.interrupt(); + } if (mNetworkStateReceiver!= null) { this.unregisterReceiver(mNetworkStateReceiver); -- cgit v1.2.3 From 61292176b4fe0543b0f4f4d5c339f2ebf0f6e1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Tue, 28 Jan 2014 20:06:23 +0100 Subject: Rebooting with no network again a again works. --- app/src/main/java/se/leap/bitmaskclient/Dashboard.java | 4 ---- app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index b95f319f..62a68d2d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -104,10 +104,6 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf protected void onPause() { super.onPause(); - Log.d("Dashboard", "eip_service_fragment.saveEipStatus()"); - EipServiceFragment eip_service_fragment = (EipServiceFragment) getFragmentManager().findFragmentByTag(TAG_EIP_FRAGMENT); - if(eip_service_fragment != null) - eip_service_fragment.saveEipStatus(); } @Override diff --git a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java index 4cc6be4f..15cc5b4d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java @@ -172,6 +172,7 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe eipStatus.setText(R.string.state_noprocess); } eipAutoSwitched = true; + saveEipStatus(); } @@ -179,6 +180,8 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe mEipStartPending = true; eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); ((TextView) eipFragment.findViewById(R.id.eipStatus)).setText(R.string.eip_status_start_pending); + eipSwitch.setChecked(true); + saveEipStatus(); eipCommand(EIP.ACTION_START_EIP); } -- cgit v1.2.3 From 38ebb49be8f195c41a30d0da4d91fc2e61846e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Wed, 9 Apr 2014 21:33:40 +0200 Subject: Gradle rebase fixes 2 different notifications bug. --- app/src/main/AndroidManifest.xml | 13 +++++++++-- .../main/java/se/leap/bitmaskclient/Dashboard.java | 25 +++++++++++----------- .../java/se/leap/bitmaskclient/OnBootReceiver.java | 24 +++++++++++++++++++++ 3 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b2366b82..0d23b069 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,7 +22,7 @@ - + - + + + + + + + diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 62a68d2d..3c17ebb8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -116,7 +116,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf Intent updateEIP = new Intent(getApplicationContext(), EIP.class); updateEIP.setAction(EIP.ACTION_UPDATE_EIP_SERVICE); startService(updateEIP); - buildDashboard(); + buildDashboard(false); invalidateOptionsMenu(); if(data != null && data.hasExtra(LogInDialog.VERB)) { View view = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0); @@ -175,18 +175,17 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf FragmentManager fragMan = getFragmentManager(); if ( provider.hasEIP()){ EipServiceFragment eipFragment = new EipServiceFragment(); - EipServiceFragment eipFragment = new EipServiceFragment(); - if (hide_and_turn_on_eip) { - getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().remove(Dashboard.START_ON_BOOT).commit(); - Bundle arguments = new Bundle(); - arguments.putBoolean(EipServiceFragment.START_ON_BOOT, true); - eipFragment.setArguments(arguments); - } - fragMan.beginTransaction().replace(R.id.servicesCollection, eipFragment, EipServiceFragment.TAG).commit(); - - if (hide_and_turn_on_eip) { - onBackPressed(); - } + if (hide_and_turn_on_eip) { + getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE).edit().remove(Dashboard.START_ON_BOOT).commit(); + Bundle arguments = new Bundle(); + arguments.putBoolean(EipServiceFragment.START_ON_BOOT, true); + eipFragment.setArguments(arguments); + } + fragMan.beginTransaction().replace(R.id.servicesCollection, eipFragment, EipServiceFragment.TAG).commit(); + + if (hide_and_turn_on_eip) { + onBackPressed(); + } } } diff --git a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java new file mode 100644 index 00000000..eb196d46 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java @@ -0,0 +1,24 @@ +package se.leap.bitmaskclient; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + + +public class OnBootReceiver extends BroadcastReceiver { + + // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { + if (!context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE).getString(Provider.KEY, "").isEmpty() && context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE).getBoolean(Dashboard.START_ON_BOOT, false)) { + Intent dashboard_intent = new Intent(context, Dashboard.class); + dashboard_intent.setAction(EIP.ACTION_START_EIP); + dashboard_intent.putExtra(Dashboard.ON_BOOT, true); + dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(dashboard_intent); + } + } + } +} -- cgit v1.2.3 From b7d3a5a1582306e380ee07cb3c2a3066e16bcf1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Mon, 21 Apr 2014 20:32:09 +0200 Subject: Nullpointers fixed. --- app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java | 6 +++++- app/src/main/java/se/leap/openvpn/OpenVpnService.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java index 15cc5b4d..4341c9dc 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java @@ -120,13 +120,17 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe protected void saveEipStatus() { boolean eip_is_on = false; + Log.d("bitmask", "saveEipStatus"); if(eipSwitch.isChecked()) { eip_is_on = true; } - getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit().putBoolean(Dashboard.START_ON_BOOT, eip_is_on).commit(); + + if(getActivity() != null) + getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit().putBoolean(Dashboard.START_ON_BOOT, eip_is_on).commit(); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + Log.d("bitmask", "onCheckChanged"); if (buttonView.equals(eipSwitch) && !eipAutoSwitched){ boolean allowed_anon = getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).getBoolean(EIP.ALLOWED_ANON, false); String certificate = getActivity().getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).getString(EIP.CERTIFICATE, ""); diff --git a/app/src/main/java/se/leap/openvpn/OpenVpnService.java b/app/src/main/java/se/leap/openvpn/OpenVpnService.java index 11071802..475ed75b 100644 --- a/app/src/main/java/se/leap/openvpn/OpenVpnService.java +++ b/app/src/main/java/se/leap/openvpn/OpenVpnService.java @@ -163,7 +163,7 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac PendingIntent getLogPendingIntent() { // Let the configure Button show the Dashboard - Intent intent = new Intent(Dashboard.getAppContext(),Dashboard.class); + Intent intent = new Intent(getApplicationContext(),Dashboard.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent startLW = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); -- cgit v1.2.3 From 3b74a663480d9d241816f0eeaf44bb326c2b8b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Tue, 22 Apr 2014 11:42:36 +0200 Subject: Save eip status while updating state. This fixes https://leap.se/code/issues/5556 --- app/src/main/java/se/leap/openvpn/OpenVpnService.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/openvpn/OpenVpnService.java b/app/src/main/java/se/leap/openvpn/OpenVpnService.java index 475ed75b..ccac72c7 100644 --- a/app/src/main/java/se/leap/openvpn/OpenVpnService.java +++ b/app/src/main/java/se/leap/openvpn/OpenVpnService.java @@ -1,13 +1,8 @@ package se.leap.openvpn; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Vector; -import se.leap.bitmaskclient.Dashboard; -import se.leap.bitmaskclient.R; import android.annotation.TargetApi; +import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -20,11 +15,17 @@ import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.net.VpnService; import android.os.Binder; -import android.os.Handler.Callback; import android.os.Build; +import android.os.Handler.Callback; import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Vector; +import se.leap.bitmaskclient.Dashboard; +import se.leap.bitmaskclient.R; import se.leap.openvpn.OpenVPN.StateListener; public class OpenVpnService extends VpnService implements StateListener, Callback { @@ -482,6 +483,8 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac boolean persist = false; if (("NOPROCESS".equals(state) ) || ("EXITING").equals(state)){ showNotification(state, getString(R.string.eip_state_not_connected), ticker, false, 0, persist); + if(getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE) != null) + getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE).edit().putBoolean(Dashboard.START_ON_BOOT, false).commit(); } else if (state.equals("GET_CONFIG") || state.equals("ASSIGN_IP")){ //don't show them in the notification message } -- cgit v1.2.3 From 3d31821eadd240ad855a1caad2d80280dd5bffef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Tue, 22 Apr 2014 17:21:57 +0200 Subject: If no vpn is running, cancel notifications. --- app/src/main/java/se/leap/openvpn/OpenVpnService.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/openvpn/OpenVpnService.java b/app/src/main/java/se/leap/openvpn/OpenVpnService.java index ccac72c7..deec8518 100644 --- a/app/src/main/java/se/leap/openvpn/OpenVpnService.java +++ b/app/src/main/java/se/leap/openvpn/OpenVpnService.java @@ -470,11 +470,16 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac public void updateState(String state,String logmessage, int resid) { // If the process is not running, ignore any state, // Notification should be invisible in this state - if(mProcessThread==null) - return; - if("CONNECTED".equals(state)) { - mNotificationManager.cancel(OPENVPN_STATUS); - } else if(!"BYTECOUNT".equals(state)) { + android.util.Log.d("OpenVpnService", "updateState(" + state + ","+logmessage); + + if(mProcessThread==null) { + if(mNotificationManager != null) + mNotificationManager.cancel(OPENVPN_STATUS); + return; + } + if("CONNECTED".equalsIgnoreCase(state)) { + mNotificationManager.cancel(OPENVPN_STATUS); + } else if(!"BYTECOUNT".equals(state)) { // Other notifications are shown, // This also mean we are no longer connected, ignore bytecount messages until next -- cgit v1.2.3 From 00030cd3b937b3be3ffbe8b390845371e7bd8aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Wed, 23 Apr 2014 22:26:13 +0200 Subject: Bump build number and version - 0.5.1RC1 --- app/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0d23b069..6d281c99 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,8 +17,8 @@ + android:versionCode="75" + android:versionName="0.5.1-RC1" > -- cgit v1.2.3 From fd96e0c9c77d1afdaa188590635597658a751879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Thu, 24 Apr 2014 00:37:08 +0200 Subject: Commercial certificate to download provider.json Removed the default danger_on = true in the debug build. --- app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java | 6 +++--- app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'app/src') diff --git a/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java index 75ef511d..9f4b8d27 100644 --- a/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java +++ b/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java @@ -467,7 +467,7 @@ public class ProviderAPI extends IntentService { broadcast_progress(progress++); CA_CERT_DOWNLOADED = true; if(!PROVIDER_JSON_DOWNLOADED) - current_download = getAndSetProviderJson(last_provider_main_url); + current_download = getAndSetProviderJson(last_provider_main_url, last_danger_on); if(PROVIDER_JSON_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) { broadcast_progress(progress++); PROVIDER_JSON_DOWNLOADED = true; @@ -518,11 +518,11 @@ public class ProviderAPI extends IntentService { return result; } - private Bundle getAndSetProviderJson(String provider_main_url) { + private Bundle getAndSetProviderJson(String provider_main_url, boolean danger_on) { Bundle result = new Bundle(); if(setting_up_provider) { - String provider_dot_json_string = downloadWithProviderCA(provider_main_url + "/provider.json", true); + String provider_dot_json_string = downloadWithCommercialCA(provider_main_url + "/provider.json", danger_on); try { JSONObject provider_json = new JSONObject(provider_dot_json_string); diff --git a/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java index 39ce1146..150b27b2 100644 --- a/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java +++ b/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java @@ -516,7 +516,7 @@ public class ProviderAPI extends IntentService { Bundle result = new Bundle(); if(setting_up_provider) { - String provider_dot_json_string = downloadWithProviderCA(provider_main_url + "/provider.json"); + String provider_dot_json_string = downloadWithCommercialCA(provider_main_url + "/provider.json"); try { JSONObject provider_json = new JSONObject(provider_dot_json_string); -- cgit v1.2.3 From 348ef1e477c88ed1fa7c20fee16da0ad30615ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Thu, 24 Apr 2014 00:52:44 +0200 Subject: Bump build number and version - 0.5.1RC2 --- app/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6d281c99..219fa12d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,8 +17,8 @@ + android:versionCode="76" + android:versionName="0.5.1-RC2" > -- cgit v1.2.3 From 03f64e60e0afed08406626875639a34f6aabadbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Sat, 26 Apr 2014 16:36:39 +0200 Subject: Bump build and version numbers for 0.5.1 release --- app/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 219fa12d..0c6d0b3f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,8 +17,8 @@ + android:versionCode="77" + android:versionName="0.5.1" > -- cgit v1.2.3