diff options
40 files changed, 2135 insertions, 642 deletions
@@ -1,8 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> + <classpathentry kind="src" path="gen"/> <classpathentry kind="src" path="src"/> <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> - <classpathentry kind="src" path="gen"/> <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> + <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/> <classpathentry kind="output" path="bin/classes"/> </classpath> @@ -1,6 +1,7 @@ obj bin gen +libs openvpn/.git openvpn/autom4te.cache openvpn/aclocal.m4 @@ -65,3 +66,6 @@ id.zip /hosts-for-android-emulator.bk /build-native.sh.bk /build-native.sh~ + +build.gradle +local.properties diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 56ca7d44..68a1ef13 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -22,10 +22,7 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <!-- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- <uses-permission android:name="com.android.vending.BILLING" /> --> @@ -158,4 +155,4 @@ </activity> </application> -</manifest>
\ No newline at end of file +</manifest> @@ -0,0 +1 @@ +README.txt
\ No newline at end of file @@ -1,43 +1,65 @@ -This is my first Android project, so some things may be done in a completely stupid way. +Compiling +========= -See the file todo.txt for ideas/not yet implemented features (and the bug tracker). +Preconditions +---------------- -Build instraction: +1. Android SDK installed (follow instructions from http://developer.android.com/sdk/index.html) +2. API version 16 or version installed. +2. Ant 1.6 or greater -Checkout google breakcode: +Instructions to compile +----------------------- -svn co http://google-breakpad.googlecode.com/svn/trunk/ google-breakpad +1. cd $PROJECT_LOCATION/leap_android +2. ./compile.sh -- Install sdk -- Install ndk +Postconditions +-------------- -Do ./build-native.sh in the root directory of the project. +1. $PROJECT_LOCATION/leap_android/bin/LEAP Android-debug.apk exists -Use eclipse with android plugins to build the project. +Running on the emulator +========================= -Optional: Copy minivpn from lib/ to assets (if you want your own compiled version) +Preconditions +----------------- +1. Android SDK is installed, and its tools are in the PATH. +2. leap_android has been compiled. +3. An avd exists in ~/.android/avd/ (if you do not have one, follow instructions from http://developer.android.com/tools/devices/managing-avds-cmdline.html) +Instructions to run on the emulator +----------------------------------- +1. cd $PROJECT_LOCATION/leap_android +1. Run script: ./run.sh @AVD-NAME . (avd names are the names of the files in ~/.android/avd with extension .avd). -Starting a VPN by name from an external app: +Postconditions +-------------- -public class StartOpenVPNActivity extends Activity { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - final String EXTRA_NAME = "se.leap.openvpn.shortcutProfileName"; +1. LEAP Android is running. - Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); - shortcutIntent.setClassName("se.leap.openvpn", "se.leap.openvpn.LaunchVPN"); - shortcutIntent.putExtra(EXTRA_NAME,"upb ssl"); - startActivity(shortcutIntent); - } -} +Debugging from console +====================== -or from the shell: +Preconditions +----------------- -am start -a android.intent.action.VPNLEGACY -n se.leap.openvpn/.LaunchVPN -e se.leap.openvpn.shortcutProfileName Home +1. Android SDK is installed, and its tools are in the PATH. +2. leap_android has been compiled. +3. An avd exists in ~/.android/avd/ (if you do not have one, follow instructions from http://developer.android.com/tools/devices/managing-avds-cmdline.html). +4. jdb is installed (this program is part of OpenJDK 7) +Instructions to debug from the console +----------------------------------- + +1. cd $PROJECT_LOCATION/leap_android +2. Run script: ./debug.sh @AVD-NAME . (avd names are the names of the files in ~/.android/avd with extension .avd). + +Postconditions +-------------- + +1. LEAP Android is running. +2. LEAP Android does not show the message "Application LEAP for Android (process se.leap.leapclient) is waiting for the debugger to attach". +3. You are in a jdb debuggin session. diff --git a/README_icsopenvpn.txt b/README_icsopenvpn.txt new file mode 100644 index 00000000..f6690f5a --- /dev/null +++ b/README_icsopenvpn.txt @@ -0,0 +1,43 @@ +This is my first Android project, so some things may be done in a completely stupid way. + +See the file todo.txt for ideas/not yet implemented features (and the bug tracker). + +Build instraction: + +Checkout google breakcode: + +svn co http://google-breakpad.googlecode.com/svn/trunk/ google-breakpad + +- Install sdk +- Install ndk + +Do ./build-native.sh in the root directory of the project. + +Use eclipse with android plugins to build the project. + +Optional: Copy minivpn from lib/ to assets (if you want your own compiled version) + + + + +Starting a VPN by name from an external app: + +public class StartOpenVPNActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + final String EXTRA_NAME = "se.leap.openvpn.shortcutProfileName"; + + Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); + shortcutIntent.setClassName("se.leap.openvpn", "se.leap.openvpn.LaunchVPN"); + shortcutIntent.putExtra(EXTRA_NAME,"upb ssl"); + startActivity(shortcutIntent); + } +} + +or from the shell: + +am start -a android.intent.action.VPNLEGACY -n se.leap.openvpn/.LaunchVPN -e se.leap.openvpn.shortcutProfileName Home + diff --git a/assets/providers/bitmask.net_provider.json b/assets/providers/bitmask.net_provider.json deleted file mode 100644 index d61be196..00000000 --- a/assets/providers/bitmask.net_provider.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "api_uri": "https://api.bitmask.net:4430", - "api_version": "1", - "ca_cert_fingerprint": "SHA256: 0f17c033115f6b76ff67871872303ff65034efe7dd1b910062ca323eb4da5c7e", - "ca_cert_uri": "https://bitmask.net/ca.crt", - "default_language": "en", - "description": { - "el": "Bitmask είναι ένα έργο του LEAP με σκοπό τον έλεγχο της απόδοσης και της αξιοπιστίας του λογισμικού LEAP. Bitmask τρέχει για τις τελευταίες αιμορραγία άκρο του κώδικα LEAP, και θα έχει πιθανότατα περισσότερες δυνατότητες και λιγότερα αξιοπιστία από άλλους φορείς παροχής υπηρεσιών.", - "en": "Bitmask is a project of LEAP with the purpose to test the performance and reliability of the LEAP software. Bitmask runs on the latest bleeding edge of the LEAP code, and will likely have more features and less reliability than other service providers.", - "es": "Bitmask es un proyecto de LEAP con el propósito de probar el rendimiento y la fiabilidad del software LEAP. Bitmask corre la última versión del código LEAP, y es de esperar que tenga más funciones y menos fiabilidad que los proveedores de servicios." - }, - "domain": "bitmask.net", - "enrollment_policy": "open", - "languages": [ - "el", - "en", - "es" - ], - "name": { - "en": "Bitmask" - }, - "services": [ - "openvpn" - ] -}
\ No newline at end of file diff --git a/assets/urls/bitmask.url b/assets/urls/bitmask.url index 910f0400..a17a5ff8 100644 --- a/assets/urls/bitmask.url +++ b/assets/urls/bitmask.url @@ -1,7 +1,7 @@ {
"name" : "bitmask",
"assets_json_provider" : "providers/bitmask.net_provider.json",
- "json_provider" : "https://bitmask.net/provider.json",
+ "provider_json_url" : "https://bitmask.net/provider.json",
"cert" : "https://bitmask.net/ca.crt",
"json_eip_service" : "https://api.bitmask.net:4430/1/config/eip-service.json"
}
\ No newline at end of file diff --git a/build.xml b/build.xml new file mode 100644 index 00000000..538f2eee --- /dev/null +++ b/build.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="LEAP Android" default="help"> + + <!-- The local.properties file is created and updated by the 'android' tool. + It contains the path to the SDK. It should *NOT* be checked into + Version Control Systems. --> + <property file="local.properties" /> + + <!-- The ant.properties file can be created by you. It is only edited by the + 'android' tool to add properties to it. + This is the place to change some Ant specific build properties. + Here are some properties you may want to change/update: + + source.dir + The name of the source directory. Default is 'src'. + out.dir + The name of the output directory. Default is 'bin'. + + For other overridable properties, look at the beginning of the rules + files in the SDK, at tools/ant/build.xml + + Properties related to the SDK location or the project target should + be updated using the 'android' tool with the 'update' action. + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. + + --> + <property file="ant.properties" /> + + <!-- if sdk.dir was not set from one of the property file, then + get it from the ANDROID_HOME env var. + This must be done before we load project.properties since + the proguard config can use sdk.dir --> + <property environment="env" /> + <condition property="sdk.dir" value="${env.ANDROID_HOME}"> + <isset property="env.ANDROID_HOME" /> + </condition> + + <!-- The project.properties file is created and updated by the 'android' + tool, as well as ADT. + + This contains project specific properties such as project target, and library + dependencies. Lower level build properties are stored in ant.properties + (or in .classpath for Eclipse projects). + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. --> + <loadproperties srcFile="project.properties" /> + + <!-- quick check on sdk.dir --> + <fail + message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." + unless="sdk.dir" + /> + + <!-- + Import per project custom build rules if present at the root of the project. + This is the place to put custom intermediary targets such as: + -pre-build + -pre-compile + -post-compile (This is typically used for code obfuscation. + Compiled code location: ${out.classes.absolute.dir} + If this is not done in place, override ${out.dex.input.absolute.dir}) + -post-package + -post-build + -pre-clean + --> + <import file="custom_rules.xml" optional="true" /> + + <!-- Import the actual build file. + + To customize existing targets, there are two options: + - Customize only one target: + - copy/paste the target into this file, *before* the + <import> task. + - customize it to your needs. + - Customize the whole content of build.xml + - copy/paste the content of the rules files (minus the top node) + into this file, replacing the <import> task. + - customize to your needs. + + *********************** + ****** IMPORTANT ****** + *********************** + In all cases you must update the value of version-tag below to read 'custom' instead of an integer, + in order to avoid having your file be overridden by tools such as "android update project" + --> + <!-- version-tag: 1 --> + <import file="${sdk.dir}/tools/ant/build.xml" /> + +</project> diff --git a/compile.sh b/compile.sh new file mode 100755 index 00000000..2c50e029 --- /dev/null +++ b/compile.sh @@ -0,0 +1,3 @@ +#!/bin/bash +android update project --path . --name "LEAP Android" --target android-16 +ant debug diff --git a/debug.sh b/debug.sh new file mode 100755 index 00000000..18679368 --- /dev/null +++ b/debug.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +if [ -z "$2" ] +then + echo Usage: debug.sh \"avd name\" \"project folder\" + exit 0; +fi +avd_name=$1 +PROJECT_FOLDER=$2 +localport=`expr $RANDOM % 65536` + +wait_until_booted() { + OUT=`adb shell getprop init.svc.bootanim` + RES="stopped" + + while [[ ${OUT:0:7} != 'stopped' ]]; do + OUT=`adb shell getprop init.svc.bootanim` +# echo 'Waiting for emulator to fully boot...' + sleep 5 + done + + echo "Emulator booted!" +} + +emulator -wipe-data @$avd_name & # If you want to test the app from scratch +wait_until_booted +adb install -r $PROJECT_FOLDER/bin/LEAP\ Android-debug.apk # Install the new version of the application +adb shell am start -D se.leap.leapclient/.Dashboard # Run app +sleep 1 +pid=`adb shell ps | grep leap | awk '{print $2}'` # Identify the process id (pid) of the current leapclient process instance +echo forwarding tcp:$localport to jwdp:$pid +adb forward tcp:$localport jdwp:$pid +jdb -sourcepath $PROJECT_FOLDER/src/ -attach localhost:$localport diff --git a/hosts-for-android-emulator b/hosts-for-android-emulator index b10103ef..ab0cf906 100644 --- a/hosts-for-android-emulator +++ b/hosts-for-android-emulator @@ -1,5 +1,6 @@ 127.0.0.1 localhost 10.0.2.2 api.lvh.me +10.0.2.2 lvh.me # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback diff --git a/libs/bcprov-ext-jdk15on-146.jar b/libs/bcprov-ext-jdk15on-146.jar Binary files differdeleted file mode 100644 index 7d8a22e4..00000000 --- a/libs/bcprov-ext-jdk15on-146.jar +++ /dev/null diff --git a/libs/jboss-common-client.jar b/libs/jboss-common-client.jar Binary files differdeleted file mode 100644 index af5fb004..00000000 --- a/libs/jboss-common-client.jar +++ /dev/null diff --git a/libs/jboss-srp-client.jar b/libs/jboss-srp-client.jar Binary files differdeleted file mode 100644 index 9a64c28a..00000000 --- a/libs/jboss-srp-client.jar +++ /dev/null diff --git a/libs/jbosssx-client.jar b/libs/jbosssx-client.jar Binary files differdeleted file mode 100644 index 5ad6fc84..00000000 --- a/libs/jbosssx-client.jar +++ /dev/null diff --git a/lint.xml b/lint.xml new file mode 100644 index 00000000..ee0eead5 --- /dev/null +++ b/lint.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<lint> +</lint>
\ No newline at end of file diff --git a/prepareTestProviderAPI.sh b/prepareTestProviderAPI.sh new file mode 100755 index 00000000..0eca85a9 --- /dev/null +++ b/prepareTestProviderAPI.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +if [ -z "$1" ] +then + echo Usage: prepareTestProviderAPI.sh \"project folder\" + exit 0; +fi + +PROJECT_FOLDER=$1 +hosts_file="hosts-for-tests" + +adb shell mount -o rw,remount -t yaffs2 /dev/block/mtdblock3 /system +adb push $PROJECT_FOLDER/$hosts_file /system/etc/hosts + +echo "Pushed $PROJECT_FOLDER/$hosts_file" diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 00000000..f2fe1559 --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/res/layout/log_in_dialog.xml b/res/layout/log_in_dialog.xml new file mode 100644 index 00000000..6f28118d --- /dev/null +++ b/res/layout/log_in_dialog.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + tools:context=".LogInDialog" > + + <EditText + android:id="@+id/username_entered" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" + android:layout_marginTop="16dp" + android:ems="10" + android:hint="@string/username_ask" + android:inputType="textUri" > + + <requestFocus /> + </EditText> + + <EditText + android:id="@+id/password_entered" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:hint="@string/password_ask" + android:inputType="textPassword" /> + +</LinearLayout>
\ No newline at end of file diff --git a/res/layout/new_provider_dialog.xml b/res/layout/new_provider_dialog.xml index 28122867..19b8f442 100644 --- a/res/layout/new_provider_dialog.xml +++ b/res/layout/new_provider_dialog.xml @@ -15,4 +15,10 @@ android:layout_marginBottom="4dp" android:hint="@string/new_provider_uri" /> + <CheckBox + android:id="@+id/danger_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/danger_checkbox" /> + </LinearLayout>
\ No newline at end of file diff --git a/res/menu/client_dashboard.xml b/res/menu/client_dashboard.xml index 87e7ae62..a8eaf795 100644 --- a/res/menu/client_dashboard.xml +++ b/res/menu/client_dashboard.xml @@ -7,5 +7,7 @@ android:title="@string/menu_settings"/> <item android:id="@+id/about_leap" android:title="@string/about" android:orderInCategory="110" /> <item android:id="@+id/legacy_interface" android:title="ICS OpenVPN Interface" android:orderInCategory="500" /> + <item android:id="@+id/login_button" android:title="@string/login_button" android:visible="false"></item> + <item android:id="@+id/logout_button" android:title="@string/logout_button" android:visible="false"></item> </menu>
\ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 2deb23fa..804964c1 100755 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -265,5 +265,35 @@ <string name="introduce_new_provider">Introduce new provider</string> <string name="save">Save</string> <string name="new_provider_uri">New provider\'s main URL</string> + <string name="valid_url_entered">It seems your URL is well formed</string> + <string name="not_valid_url_entered">It seems your URL is not well formed</string> + <string name="username_ask">Introduce your username</string> + <string name="password_ask">Enter your password</string> + <string name="log_in_button">Log in</string> + <string name="login_button">Log In</string> + <string name="logout_button">Log Out</string> + <string name="danger_checkbox">Trust completely</string> + <string name="setup_error_title">Configuration Error</string> + <string name="setup_error_configure_button">Configure</string> + <string name="setup_error_close_button">Exit</string> + <string name="setup_error_text">There was an error configuring LEAP with your chosen provider.\n\nYou may choose to reconfigure, or exit and configure a provider upon next launch.</string> + <string name="config_wait_title">Configuring LEAP provider</string> + <string name="config_connecting_provider">Downloading provider configuration</string> + <string name="config_downloading_services">Downloading service definitions</string> + <string name="config_downloading_certificates">Downloading authentication certificates</string> + <string name="config_error_parsing">Error parsing provider\'s responses!</string> + <string name="success">Success!</string> + <string name="incorrectly_downloaded_json_files_message">You have not entered a LEAP provider URL or it is unavailable</string> + <string name="correctly_downloaded_json_files_message">Your anon cert has been correctly downloaded</string> + <string name="incorrectly_downloaded_certificate_message">Your anon cert was not downloaded</string> + <string name="incorrectly_updated_provider_dot_json_message">Install a new version of this app.</string> + <string name="not_valid_password_message">Your password is not well-formed: it should have at least 8 characters.</string> + <string name="succesful_authentication_message">Authentication succeeded.</string> + <string name="authentication_failed_message">Authentication failed.</string> + <string name="successful_log_out_message">Logged out.</string> + <string name="log_out_failed_message">Didn\'t logged out.</string> + <string name="successful_authed_cert_downloaded_message">Your own cert has been correctly downloaded.</string> + <string name="authed_cert_download_failed_message">Your own cert has incorrectly been downloaded.</string> + </resources>
\ No newline at end of file diff --git a/res/values/values-nl/arrays.xml b/res/values/values-nl/arrays.xml new file mode 100755 index 00000000..9be2a9d4 --- /dev/null +++ b/res/values/values-nl/arrays.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--Generated by crowdin.net--> +<resources> + <string-array name="vpn_types"> + <item>Certificaten</item> + <item>PKCS12 Bestand</item> + <item>Android Certificaat</item> + <item>Gebruikersnaam/Wachtwoord</item> + <item>Statische Sleutels</item> + <item>Gebruiker/WW + Certificaten</item> + <item>Gebruiker/WW + PKCS12 </item> + <item>Gebruiker/WW + Android</item> + </string-array> + <string-array name="tls_directions_entries"> + <item>0</item> + <item>1</item> + <item>Niet-gespecificeerd</item> + </string-array> + <string-array name="verb_entries"> + <item>0 - Geen logboek</item> + <item>1 - Standaard logboek</item> + <item>2 - Uitgebreid logboek</item> + <item>3</item> + <item>4</item> + <item>5 - Debug logboek</item> + </string-array> +</resources> diff --git a/res/values/values-nl/strings.xml b/res/values/values-nl/strings.xml new file mode 100755 index 00000000..459c80ec --- /dev/null +++ b/res/values/values-nl/strings.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--Generated by crowdin.net--> +<resources> + <string name="app">LEAP voor Android</string> + <string name="address">Server Adres:</string> + <string name="port">Server Poort:</string> + <string name="location">Locatie</string> + <string name="select">Selecteer</string> + <string name="cancel">Annuleer</string> + <string name="no_data">Geen Gegevens</string> + <string name="useLZO">LZO Compressie</string> + <string name="client_no_certificate">Geen Certificaat</string> + <string name="client_certificate_title">Client Certificaat</string> + <string name="client_key_title">Client Certificaat Sleutel</string> + <string name="client_pkcs12_title">PKCS12 Bestand</string> + <string name="ca_title">CA Certificaat</string> + <string name="about">Over</string> + <string name="about_summary">Over LEAP voor Android</string> + <string name="vpn_list_summary">Lijst van alle geconfigureerde VPN verbindingen</string> + <string name="vpn_list_title">VPN Profielen</string> + <string name="vpn_type">Type</string> + <string name="pkcs12pwquery">PKCS12 Wachtwoord</string> + <string name="file_select">Selecteer…</string> + <string name="useTLSAuth">Gebruik TLS autentificatie</string> + <string name="tls_direction">TLS Richting</string> + <string name="ipv6_dialog_tile">Voer een IPv6 Adres/Netmask in met het CIDR Formaat (v.b. 2000:dd::23/64)</string> + <string name="ipv4_dialog_title">Voer een IPv4 Adres/Netmask in met het CIDR Formaat (v.b. 1.2.3.4/24)</string> + <string name="ipv4_address">IPv4 Adres</string> + <string name="ipv6_address">IPv4 Adres</string> + <string name="auth_username">Gebruikersnaam</string> + <string name="auth_pwquery">wachtwoord</string> + <string name="configure_the_vpn">VPN configureren</string> + <string name="menu_add_profile">Profiel toevoegen</string> + <string name="add_profile_name_prompt">Voer een naam in voor het nieuwe Profiel</string> + <string name="profilename">Profiel name</string> + <string name="no_error_found">Geen fout.</string> + <string name="config_error_found">Fout in de configuratie</string> + <string name="vpn_shortcut">Open VPN shortcut</string> + <string name="vpn_launch_title">Met VPN verbinden</string> + <string name="shortcut_profile_notfound">Het profiel zoals aangegeven in de snelkoppeling kon niet gevonden worden.</string> + <string name="random_host_prefix">Willekeurig Host Voorvoegsel</string> + <string name="random_host_summary">Voegt 6 willekeurige tekens toe voor de hostname</string> + <string name="custom_config_title">Eigen configuratie opties</string> + <string name="custom_config_summary">Geef je eigen configuratieopties aan. Wees voorzichtig!</string> + <string name="route_rejected">Route geweigert door Android</string> + <string name="cancel_connection">Verbinding verbreken</string> + <string name="clear_log">logboek wissen</string> + <string name="title_cancel">Annuleer bevestiging</string> + <string name="cancel_connection_query">Sluit de verbonden VPN af/annuleer de verbindingspoging?</string> + <string name="remove_vpn">VPN wissen</string> + <string name="check_remote_tlscert">Checkt of de server een TLS server certificaat gebruikt.</string> + <string name="remote_tlscn_check_title">Controleer Certificaat Hostname</string> + <string name="enter_tlscn_title">Externe Hostname(CN)</string> + <string name="tls_auth_file">TLS Auth Bestand</string> + <string name="pull_on_summary">Vraag IP adres, routes en timing opties van de server.</string> + <string name="use_pull">Pull Instellingen</string> + <string name="dns">DNS</string> + <string name="override_dns">DNS Instellingen van Server Overschrijven</string> + <string name="dns_override_summary">Gebruik eigen DNS Servers</string> + <string name="searchdomain">Zoekd domein</string> + <string name="dns1_summary">Primaire DNS server</string> + <string name="dns_server">DNS Server</string> + <string name="secondary_dns_message">Secundaire DNS server. Deze wordt gebruikt voor het geval dat de primaire DNS server niet bereikbaar is</string> + <string name="backup_dns">Backup DNS server</string> + <string name="ignored_pushed_routes">Negeer ontvangen routes</string> + <string name="ignore_routes_summary">Negeer routes ontvangen van de server.</string> + <string name="default_route_summary">Leid al het Verkeer over de VPN</string> + <string name="use_default_title">Gebruik standaard Route</string> + <string name="custom_routes_title">Eigen routes</string> + <string name="float_summary">Geverifieerde pakketen zijn vanuit elk IP toegestaan</string> + <string name="float_title">Zwevende server toestaan</string> + <string name="custom_options_title">Aangepaste Opties</string> + <string name="edit_vpn">VPN Instellingen Bewerken</string> + <string name="error">"Fout:"</string> + <string name="clear">Leeg maken</string> + <string name="info">info</string> + <string name="show_connection_details">Details van de verbinding weergeven</string> + <string name="last_openvpn_tun_config">Laatste interfaceconfiguratie van OpenVPN:</string> + <string name="local_ip_info">Lokaal IPv4: %1$s/%2$d IPv6: %3$s MTU: %4$d</string> + <string name="dns_server_info">DNS Server: %s</string> + <string name="dns_domain_info">DNS Domein: %s</string> + <string name="routes_info">Routes: %s</string> + <string name="routes_info6">Routes IPv6: %s</string> + <string name="version_info">%1$s %2$s</string> + <string name="send_logfile">Logboek verzenden</string> + <string name="send">Verzenden</string> + <string name="tap_mode">Tap mode</string> + <string name="faq_tap_mode">De VPN API van Android werkt zonder rooten van de telefoon en ondersteunt alleen de tun modus. Daarom is de tap modus niet mogelijk met deze app.</string> + <string name="import_configuration_file">configuratie bestand importeren</string> + <string name="faq_security_title">Beveiligingsoverwegingen</string> + <string name="import_vpn">Importeren</string> + <string name="broken_image_cert_title">Fout bij het weergeven van de certificaat selectie</string> + <string name="ipv4">IPv4</string> + <string name="ipv6">IPv6</string> + <string name="speed_waiting">Wachten op status bericht…</string> + <string name="converted_profile">Geïmporteerd profiel</string> + <string name="converted_profile_i">Geïmporteerd profiel %d</string> + <string name="broken_images">Niet Werkende Afbeeldingen</string> + <string name="error_empty_username">De gebruikersnaam moet niet leeg zijn.</string> + <string name="pkcs12_file_encryption_key">PKCS12 Bestand Encryptie Sleutel</string> + <string name="private_key_password">Privé Sleutel Wachtwoord</string> + <string name="password">Wachtwoord</string> + <string name="file_icon">bestands pictogram</string> + <string name="tls_authentication">TLS Verificatie</string> + <string name="generated_config">Gegenereerde Configuratie</string> + <string name="generalsettings">Algemene Instellingen</string> + <string name="ipdns">IP en DNS</string> +</resources> @@ -0,0 +1,38 @@ +#!/bin/bash + +if [ -z "$2" ] +then + echo Usage: run.sh \"avd name\" \"project folder\" + exit 0; +fi +avd_name=$1 +PROJECT_FOLDER=$2 +localport=`expr $RANDOM % 65536` + +wait_until_booted() { + OUT=`adb shell getprop init.svc.bootanim` + RES="stopped" + + while [[ ${OUT:0:7} != 'stopped' ]]; do + OUT=`adb shell getprop init.svc.bootanim` +# echo 'Waiting for emulator to fully boot...' + sleep 5 + done + + echo "Emulator booted!" +} + +echo "Press \"y\" key and enter if you want to wipe emulator's data" +read wipe_data_or_not +if [ $wipe_data_or_not == "y" ] +then + echo "Wiping data" + emulator -wipe-data @$avd_name & # If you want to test the app from scratch +else + echo "Not wiping data" + emulator @$avd_name & # If you want to test the app from scratch +fi + +wait_until_booted +adb install -r $PROJECT_FOLDER/bin/LEAP\ Android-debug.apk # Install the new version of the application +adb shell am start se.leap.leapclient/.Dashboard # Run app diff --git a/src/org/jboss/security/srp/SRPParameters.java b/src/org/jboss/security/srp/SRPParameters.java new file mode 100644 index 00000000..4b188cb3 --- /dev/null +++ b/src/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/src/se/leap/leapclient/ConfigHelper.java b/src/se/leap/leapclient/ConfigHelper.java index 7476c89a..dd20112c 100644 --- a/src/se/leap/leapclient/ConfigHelper.java +++ b/src/se/leap/leapclient/ConfigHelper.java @@ -1,11 +1,8 @@ package se.leap.leapclient; - -import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileWriter; import java.io.IOException; import java.math.BigInteger; import java.io.InputStream; @@ -19,86 +16,188 @@ import java.security.cert.X509Certificate; import org.json.JSONException; import org.json.JSONObject; +import android.content.Context; import android.content.SharedPreferences; import android.os.Environment; -import android.util.Log; +/** + * Stores constants, and implements auxiliary methods used across all LEAP Android classes. + * + * @author parmegv + * @author MeanderingCode + * + */ public class ConfigHelper { public static SharedPreferences shared_preferences; private static KeyStore keystore_trusted; - final static String downloadJsonFilesBundleExtra = "downloadJSONFiles"; - final static String downloadNewProviderDotJSON = "downloadNewProviderDotJSON"; - public static final String srpRegister = "srpRegister"; - final public static String srpAuth = "srpAuth"; - final public static String resultKey = "result"; - final static String provider_key = "provider"; - final static String cert_key = "cert"; - final static String eip_service_key = "eip"; - public static final String PREFERENCES_KEY = "LEAPPreferences"; - public static final String user_directory = "leap_android"; - public static String provider_main_url = "provider_main_url"; - final public static String srp_server_url_key = "srp_server_url"; - final public static String username_key = "username"; - final public static String password_key = "password"; - final public static String eip_service_api_path = "/config/eip-service.json"; - - final public static String NG_1024 = - "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3"; - final public static BigInteger g = BigInteger.valueOf(2); + final public static String + DOWNLOAD_JSON_FILES_BUNDLE_EXTRA = "downloadJSONFiles", + UPDATE_PROVIDER_DOTJSON = "updateProviderDotJSON", + DOWNLOAD_NEW_PROVIDER_DOTJSON = "downloadNewProviderDotJSON", + LOG_IN_DIALOG = "logInDialog", + NEW_PROVIDER_DIALOG = "logInDialog", + SRP_REGISTER = "srpRegister", + SRP_AUTH = "srpAuth", + M1_KEY = "M1", + M2_KEY = "M2", + LOG_IN = "logIn", + LOG_OUT = "logOut", + DOWNLOAD_CERTIFICATE = "downloadUserAuthedCertificate", + API_VERSION_KEY = "api_version", + RESULT_KEY = "result", + RECEIVER_KEY = "receiver", + PROVIDER_KEY = "provider", + SERVICE_KEY = "service", + ALLOWED_ANON = "allow_anonymous", + MAIN_CERT_KEY = "main_cert", + CERT_KEY = "cert", + EIP_SERVICE_KEY = "eip", + TYPE_OF_CERTIFICATE = "type_of_certificate", + ANON_CERTIFICATE = "anon_certificate", + AUTHED_CERTIFICATE = "authed_certificate", + SALT_KEY = "salt", + SESSION_ID_COOKIE_KEY = "session_id_cookie_key", + SESSION_ID_KEY = "session_id", + PREFERENCES_KEY = "LEAPPreferences", + USER_DIRECTORY = "leap_android", + PROVIDER_NAME = "provider_name", + PROVIDER_MAIN_URL = "provider_main_url", + PROVIDER_JSON_URL = "provider_json_url", + CUSTOM = "custom", + DANGER_ON = "danger_on", + API_URL_KEY = "api_uri", + USERNAME_KEY = "username", + PASSWORD_KEY = "password", + ALLOW_REGISTRATION_KEY = "allow_registration", + EIP_SERVICE_API_PATH = "config/eip-service.json", + ERRORS_KEY = "errors" + ; - final public static int CUSTOM_PROVIDER_ADDED = 0; - final public static int CORRECTLY_DOWNLOADED_JSON_FILES = 1; - final public static int INCORRECTLY_DOWNLOADED_JSON_FILES = 2; - final public static int SRP_AUTHENTICATION_SUCCESSFUL = 3; - final public static int SRP_AUTHENTICATION_FAILED = 4; - public static final int SRP_REGISTRATION_SUCCESSFUL = 5; - public static final int SRP_REGISTRATION_FAILED = 6; + final public static String NG_1024 = + "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3"; + final public static BigInteger G = new BigInteger("2"); - static void saveSharedPref(String shared_preferences_key, JSONObject content) { + final public static int + CUSTOM_PROVIDER_ADDED = 0, + CORRECTLY_DOWNLOADED_JSON_FILES = 1, + INCORRECTLY_DOWNLOADED_JSON_FILES = 2, + 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, + CORRECTLY_UPDATED_PROVIDER_DOT_JSON = 11, + INCORRECTLY_UPDATED_PROVIDER_DOT_JSON = 12, + CORRECTLY_DOWNLOADED_ANON_CERTIFICATE = 13, + INCORRECTLY_DOWNLOADED_ANON_CERTIFICATE = 14 + ; + + + private static boolean checkSharedPrefs() { + try { + shared_preferences = Dashboard.getAppContext().getSharedPreferences(PREFERENCES_KEY,Context.MODE_PRIVATE); + } catch (Exception e) { + return false; + } + + return true; + } + + /** + * Saves a JSON object into class scope Shared Preferences + * @param shared_preferences_key + * @param content + */ + public static void saveSharedPref(String shared_preferences_key, JSONObject content) { SharedPreferences.Editor shared_preferences_editor = shared_preferences .edit(); shared_preferences_editor.putString(shared_preferences_key, content.toString()); shared_preferences_editor.commit(); - System.out.println("Shared preferences updated: key = " - + shared_preferences_key - + " Content = " - + shared_preferences.getString( - shared_preferences_key, "Default")); } - static void rescueJSONException(JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + /** + * Saves a String object into class scope Shared Preferences + * @param shared_preferences_key + * @param content + */ + public static void saveSharedPref(String shared_preferences_key, String content) { + + SharedPreferences.Editor shared_preferences_editor = shared_preferences + .edit(); + shared_preferences_editor.putString(shared_preferences_key, + content); + shared_preferences_editor.commit(); } - static void saveFile(String filename, String content) { - File root = Environment.getExternalStorageDirectory(); - File leap_dir = new File(root.getAbsolutePath() + File.separator + user_directory); - if (!leap_dir.isDirectory()) { - leap_dir.mkdir(); + /** + * Saves a boolean object into class scope Shared Preferences + * @param shared_preferences_key + * @param content + */ + public static void saveSharedPref(String shared_preferences_key, boolean content) { + + SharedPreferences.Editor shared_preferences_editor = shared_preferences + .edit(); + shared_preferences_editor.putBoolean(shared_preferences_key, content); + shared_preferences_editor.commit(); + } + + /** + * Gets String object from class scope Shared Preferences + * @param shared_preferences_key + * @return the string correspondent to the key parameter + */ + public static String getStringFromSharedPref(String shared_preferences_key) { + String content = null; + if ( checkSharedPrefs() ) { + content = shared_preferences.getString(shared_preferences_key, ""); } - try { - if (!leap_dir.isDirectory()) { - throw new IOException( - "Unable to create directory " + user_directory + ". Maybe the SD card is mounted?"); - } - File outputFile = new File(leap_dir, filename); - BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile)); - writer.write(content); - writer.close(); - } catch (IOException e) { - Log.w("leap_android", e.getMessage(), e); + return content; + } + + /** + * Gets JSON object from class scope Shared Preferences + * @param shared_preferences_key + * @return the JSON object correspondent to the key parameter + */ + public static JSONObject getJsonFromSharedPref(String shared_preferences_key) throws JSONException { + JSONObject content = null; + if ( checkSharedPrefs() ) { + content = new JSONObject( shared_preferences.getString(shared_preferences_key, "") ); } + + return content; } - static FileInputStream openFileInputStream(String filename) { + /* + * This method defaults to false. + * If you use this method, be sure to fail-closed on false! + * TODO This is obviously less than ideal...solve it! + */ + public static boolean getBoolFromSharedPref(String shared_preferences_key) { + boolean value = false; + if ( checkSharedPrefs() ) { + value = shared_preferences.getBoolean(shared_preferences_key, false); + } + return value; + } + + /** + * Opens a FileInputStream from the user directory of the external storage directory. + * @param filename + * @return a file input stream + */ + public static FileInputStream openFileInputStream(String filename) { FileInputStream input_stream = null; File root = Environment.getExternalStorageDirectory(); - File leap_dir = new File(root.getAbsolutePath() + File.separator + user_directory); + File leap_dir = new File(root.getAbsolutePath() + File.separator + USER_DIRECTORY); try { input_stream = new FileInputStream(leap_dir + File.separator + filename); } catch (FileNotFoundException e) { @@ -108,11 +207,40 @@ public class ConfigHelper { return input_stream; } + /** + * 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; + } + + /** + * Sets class scope Shared Preferences + * @param shared_preferences + */ public static void setSharedPreferences( SharedPreferences shared_preferences) { ConfigHelper.shared_preferences = shared_preferences; } + /** + * 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 { @@ -129,14 +257,22 @@ public class ConfigHelper { } } + /** + * 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) { String filename_to_save = provider + "_certificate.cer"; - saveFile(filename_to_save, certificate); CertificateFactory cf; try { cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate)cf.generateCertificate(openFileInputStream(filename_to_save)); + if(keystore_trusted == null) { + keystore_trusted = KeyStore.getInstance("BKS"); + keystore_trusted.load(null); + } keystore_trusted.setCertificateEntry(provider, cert); } catch (CertificateException e) { // TODO Auto-generated catch block @@ -144,43 +280,19 @@ public class ConfigHelper { } catch (KeyStoreException e) { // TODO Auto-generated catch block e.printStackTrace(); - } - } - - public static KeyStore getKeystore() { - return keystore_trusted; - } - - public static void getNewKeystore(InputStream leap_keystore) { - try { - keystore_trusted = KeyStore.getInstance("BKS"); - try { - // Initialize the keystore with the provided trusted certificates - // Also provide the password of the keystore - keystore_trusted.load(leap_keystore, "uer92jf".toCharArray()); - //keystore_trusted.load(null, null); - } finally { - leap_keystore.close(); - } - } 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(); } } - - public static int getSrpAuthenticationFailed() { - return SRP_AUTHENTICATION_FAILED; - }static String extractProviderName(String provider_main_url) { - - return null; + + /** + * @return class wide keystore + */ + public static KeyStore getKeystore() { + return keystore_trusted; } } diff --git a/src/se/leap/leapclient/ConfigurationWizard.java b/src/se/leap/leapclient/ConfigurationWizard.java index 0d445227..ce279425 100644 --- a/src/se/leap/leapclient/ConfigurationWizard.java +++ b/src/se/leap/leapclient/ConfigurationWizard.java @@ -1,21 +1,20 @@ package se.leap.leapclient;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
import java.util.Iterator;
-import java.util.Scanner;
import org.json.JSONException;
import org.json.JSONObject;
import se.leap.leapclient.ProviderAPIResultReceiver.Receiver;
import se.leap.leapclient.ProviderListContent.ProviderItem;
+import se.leap.leapclient.R;
import android.app.Activity;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
+import android.app.ProgressDialog;
import android.content.Intent;
import android.content.res.AssetManager;
import android.os.Bundle;
@@ -23,33 +22,24 @@ import android.os.Handler; import android.view.View;
import android.widget.Toast;
-
/**
- * An activity representing a list of Providers. This activity
- * has different presentations for handset and tablet-size devices. On
- * handsets, the activity presents a list of items, which when touched,
- * lead to a {@link DashboardActivity} representing
- * item details. On tablets, the activity presents the list of items and
- * item details side-by-side using two vertical panes.
- * <p>
- * The activity makes heavy use of fragments. The list of items is a
- * {@link ProviderListFragment} and the item details
- * (if present) is a {@link DashboardFragment}.
- * <p>
- * This activity also implements the required
- * {@link ProviderListFragment.Callbacks} interface
- * to listen for item selections.
+ * 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, NewProviderDialog.NewProviderDialogInterface, Receiver {
+implements ProviderListFragment.Callbacks, NewProviderDialog.NewProviderDialogInterface, Receiver {
-
- /**
- * Whether or not the activity is in two-pane mode, i.e. running on a tablet
- * device.
- */
- private boolean mTwoPane;
+ private ProviderItem mSelectedProvider;
+ private ProgressDialog mProgressDialog;
+ private Intent mConfigState = new Intent();
+ protected static final String PROVIDER_SET = "PROVIDER SET";
+ protected static final String SERVICES_RETRIEVED = "SERVICES RETRIEVED";
+
public ProviderAPIResultReceiver providerAPI_result_receiver;
@Override
@@ -57,16 +47,14 @@ public class ConfigurationWizard extends Activity super.onCreate(savedInstanceState);
setContentView(R.layout.activity_configuration_wizard);
+
+ providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler());
+ providerAPI_result_receiver.setReceiver(this);
ConfigHelper.setSharedPreferences(getSharedPreferences(ConfigHelper.PREFERENCES_KEY, MODE_PRIVATE));
loadPreseededProviders();
- if(ConfigHelper.getKeystore() == null) {
- InputStream keystore_input_stream = getResources().openRawResource(R.raw.leapkeystore);
- ConfigHelper.getNewKeystore(keystore_input_stream);
- }
-
// Only create our fragments if we're not restoring a saved instance
if ( savedInstanceState == null ){
// TODO Some welcome screen?
@@ -82,7 +70,103 @@ public class ConfigurationWizard extends Activity // TODO: If exposing deep links into your app, handle intents here.
}
- private void loadPreseededProviders() {
+ @Override
+ public void onReceiveResult(int resultCode, Bundle resultData) {
+ if(resultCode == ConfigHelper.CUSTOM_PROVIDER_ADDED){
+ ProviderListFragment providerList = new ProviderListFragment();
+
+ FragmentManager fragmentManager = getFragmentManager();
+ fragmentManager.beginTransaction()
+ .replace(R.id.configuration_wizard_layout, providerList, "providerlist")
+ .commit();
+ }
+ else if(resultCode == ConfigHelper.CORRECTLY_UPDATED_PROVIDER_DOT_JSON) {
+ JSONObject provider_json;
+ try {
+ provider_json = new JSONObject(resultData.getString(ConfigHelper.PROVIDER_KEY));
+ boolean danger_on = resultData.getBoolean(ConfigHelper.DANGER_ON);
+ ConfigHelper.saveSharedPref(ConfigHelper.PROVIDER_KEY, provider_json);
+ ConfigHelper.saveSharedPref(ConfigHelper.DANGER_ON, danger_on);
+ ConfigHelper.saveSharedPref(ConfigHelper.ALLOWED_ANON, provider_json.getJSONObject(ConfigHelper.SERVICE_KEY).getBoolean(ConfigHelper.ALLOWED_ANON));
+
+ mConfigState.setAction(PROVIDER_SET);
+
+ mProgressDialog.setMessage(getResources().getString(R.string.config_downloading_services));
+ downloadJSONFiles(mSelectedProvider);
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+
+ mProgressDialog.dismiss();
+ Toast.makeText(this, getResources().getString(R.string.config_error_parsing), Toast.LENGTH_LONG);
+ setResult(RESULT_CANCELED, mConfigState);
+ finish();
+ }
+ }
+ else if(resultCode == ConfigHelper.INCORRECTLY_UPDATED_PROVIDER_DOT_JSON) {
+ mProgressDialog.dismiss();
+ Toast.makeText(getApplicationContext(), R.string.incorrectly_updated_provider_dot_json_message, Toast.LENGTH_LONG).show();
+ setResult(RESULT_CANCELED, mConfigState);
+ finish();
+ }
+ else if(resultCode == ConfigHelper.CORRECTLY_DOWNLOADED_JSON_FILES) {
+ if (ConfigHelper.getBoolFromSharedPref(ConfigHelper.ALLOWED_ANON)){
+ mProgressDialog.setMessage(getResources().getString(R.string.config_downloading_certificates));
+ mConfigState.putExtra(SERVICES_RETRIEVED, true);
+ downloadAnonCert();
+ } else {
+ mProgressDialog.dismiss();
+ Toast.makeText(getApplicationContext(), R.string.success, Toast.LENGTH_LONG).show();
+ setResult(RESULT_OK);
+ finish();
+ }
+ }
+ else if(resultCode == ConfigHelper.INCORRECTLY_DOWNLOADED_JSON_FILES) {
+ Toast.makeText(getApplicationContext(), R.string.incorrectly_downloaded_json_files_message, Toast.LENGTH_LONG).show();
+ setResult(RESULT_CANCELED, mConfigState);
+ finish();
+ }
+ else if(resultCode == ConfigHelper.CORRECTLY_DOWNLOADED_CERTIFICATE) {
+ mProgressDialog.dismiss();
+ Toast.makeText(getApplicationContext(), R.string.correctly_downloaded_json_files_message, Toast.LENGTH_LONG).show();
+ Toast.makeText(getApplicationContext(), R.string.success, Toast.LENGTH_LONG).show();
+ //mConfigState.putExtra(CERTIFICATE_RETRIEVED, true); // If this isn't the last step and finish() is moved...
+ setResult(RESULT_OK);
+ finish();
+ } else if(resultCode == ConfigHelper.INCORRECTLY_DOWNLOADED_CERTIFICATE) {
+ mProgressDialog.dismiss();
+ Toast.makeText(getApplicationContext(), R.string.incorrectly_downloaded_certificate_message, Toast.LENGTH_LONG).show();
+ setResult(RESULT_CANCELED, mConfigState);
+ finish();
+ }
+ }
+
+ /**
+ * 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
+ Iterator<ProviderItem> preseeded_providers_iterator = ProviderListContent.ITEMS.iterator();
+ while(preseeded_providers_iterator.hasNext())
+ {
+ ProviderItem provider = preseeded_providers_iterator.next();
+ if(provider.id.equalsIgnoreCase(id))
+ {
+ mProgressDialog = ProgressDialog.show(this, getResources().getString(R.string.config_wait_title), getResources().getString(R.string.config_connecting_provider), true);
+ mSelectedProvider = provider;
+ saveProviderJson(mSelectedProvider);
+ }
+ }
+ }
+
+ /**
+ * 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 {
@@ -95,126 +179,131 @@ public class ConfigurationWizard extends Activity 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), custom));
+ ProviderListContent.addItem(new ProviderItem(provider_name, asset_manager.open(url_files_folder + "/" + url_filepath), custom, true)); // By default, it trusts the provider
+ loaded_preseeded_providers = true;
}
} catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
+ loaded_preseeded_providers = false;
+ }
+
+ return loaded_preseeded_providers;
}
- /**
- * Callback method from {@link ProviderListFragment.Callbacks}
- * indicating that the item with the given ID was selected.
+ /**
+ * Saves provider.json file associated with provider.
+ *
+ * If the provider is custom, the file has already been downloaded so we load it from memory.
+ * If not, the file is updated using the provider's URL.
+ * @param provider
*/
- @Override
- public void onItemSelected(String id) {
- if (mTwoPane) {
- // TODO Hmmm...is this how we should do this? What if it /is/ two pane?
- } else {
- // In single-pane mode, simply start the detail activity
- // for the selected item ID.
-
- Iterator<ProviderItem> preseeded_providers_iterator = ProviderListContent.ITEMS.iterator();
- while(preseeded_providers_iterator.hasNext())
- {
- ProviderItem current_provider_item = preseeded_providers_iterator.next();
- if(current_provider_item.id.equalsIgnoreCase(id))
- {
- try {
- saveProviderJson(current_provider_item);
- downloadJSONFiles(current_provider_item);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- }
+ private void saveProviderJson(ProviderItem provider) {
+ JSONObject provider_json = new JSONObject();
+ try {
+ if(!provider.custom) {
+ updateProviderDotJson(provider.name, provider.provider_json_url, provider.danger_on);
+ } else {
+ // FIXME!! We should we be updating our seeded providers list at ConfigurationWizard onStart() ?
+ // I think yes, but if so, where does this list live? leap.se, as it's the non-profit project for the software?
+ // If not, we should just be getting names/urls, and fetching the provider.json like in custom entries
+ provider_json = provider.provider_json;
+ ConfigHelper.saveSharedPref(ConfigHelper.PROVIDER_KEY, provider_json);
+ ConfigHelper.saveSharedPref(ConfigHelper.ALLOWED_ANON, provider_json.getJSONObject(ConfigHelper.SERVICE_KEY).getBoolean(ConfigHelper.ALLOWED_ANON));
+ ConfigHelper.saveSharedPref(ConfigHelper.DANGER_ON, provider.danger_on);
+
+ mProgressDialog.setMessage(getResources().getString(R.string.config_downloading_services));
+ downloadJSONFiles(mSelectedProvider);
+ }
+ } catch (JSONException e) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
}
- private void saveProviderJson(ProviderItem current_provider_item) {
- AssetManager assets_manager = getAssets();
- JSONObject provider_json = new JSONObject();
- try {
- String provider_contents = "";
- if(!current_provider_item.custom)
- provider_contents = new Scanner(new InputStreamReader(assets_manager.open(current_provider_item.provider_json_filename))).useDelimiter("\\A").next();
- else
- provider_contents = new Scanner(ConfigHelper.openFileInputStream(current_provider_item.provider_json_filename)).useDelimiter("\\A").next();
- provider_json = new JSONObject(provider_contents);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (JSONException e) {
- ConfigHelper.rescueJSONException(e);
- }
- ConfigHelper.saveSharedPref(ConfigHelper.provider_key, provider_json);
- }
-
- private void downloadJSONFiles(ProviderItem current_provider_item) throws IOException {
- providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler());
- providerAPI_result_receiver.setReceiver(this);
-
+ /**
+ * Asks ProviderAPI to download provider site's certificate and eip-service.json
+ *
+ * URLs are fetched from the provider parameter
+ * @param provider from which certificate and eip-service.json files are going to be downloaded
+ */
+ private void downloadJSONFiles(ProviderItem provider) {
Intent provider_API_command = new Intent(this, ProviderAPI.class);
Bundle method_and_parameters = new Bundle();
- method_and_parameters.putString(ConfigHelper.provider_key, current_provider_item.name);
- method_and_parameters.putString(ConfigHelper.cert_key, current_provider_item.cert_json_url);
- method_and_parameters.putString(ConfigHelper.eip_service_key, current_provider_item.eip_service_json_url);
+ method_and_parameters.putString(ConfigHelper.PROVIDER_KEY, provider.name);
+ method_and_parameters.putString(ConfigHelper.MAIN_CERT_KEY, provider.cert_json_url);
+ method_and_parameters.putString(ConfigHelper.EIP_SERVICE_KEY, provider.eip_service_json_url);
+ method_and_parameters.putBoolean(ConfigHelper.DANGER_ON, provider.danger_on);
- provider_API_command.putExtra(ConfigHelper.downloadJsonFilesBundleExtra, method_and_parameters);
- provider_API_command.putExtra("receiver", providerAPI_result_receiver);
+ provider_API_command.putExtra(ConfigHelper.DOWNLOAD_JSON_FILES_BUNDLE_EXTRA, method_and_parameters);
+ provider_API_command.putExtra(ConfigHelper.RECEIVER_KEY, providerAPI_result_receiver);
startService(provider_API_command);
}
+ /**
+ * Asks ProviderAPI to download an anonymous (anon) VPN certificate.
+ */
+ private void downloadAnonCert() {
+ Intent provider_API_command = new Intent(this, ProviderAPI.class);
+
+ Bundle method_and_parameters = new Bundle();
+
+ method_and_parameters.putString(ConfigHelper.TYPE_OF_CERTIFICATE, ConfigHelper.ANON_CERTIFICATE);
+
+ provider_API_command.putExtra(ConfigHelper.DOWNLOAD_CERTIFICATE, method_and_parameters);
+ provider_API_command.putExtra(ConfigHelper.RECEIVER_KEY, providerAPI_result_receiver);
+
+ startService(provider_API_command);
+ }
+
+ /**
+ * Open the new provider dialog
+ * @param view from which the dialog is showed
+ */
public void addNewProvider(View view) {
FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction();
- Fragment previous_new_provider_dialog = getFragmentManager().findFragmentByTag("newProviderDialog");
+ Fragment previous_new_provider_dialog = getFragmentManager().findFragmentByTag(ConfigHelper.NEW_PROVIDER_DIALOG);
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");
+ newFragment.show(fragment_transaction, ConfigHelper.NEW_PROVIDER_DIALOG);
}
@Override
- public void saveProvider(String provider_main_url) {
- providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler());
- providerAPI_result_receiver.setReceiver(this);
-
+ public void saveProvider(String provider_main_url, boolean danger_on) {
Intent provider_API_command = new Intent(this, ProviderAPI.class);
Bundle method_and_parameters = new Bundle();
- method_and_parameters.putString(ConfigHelper.provider_main_url, provider_main_url);
+ method_and_parameters.putString(ConfigHelper.PROVIDER_MAIN_URL, provider_main_url);
+ method_and_parameters.putBoolean(ConfigHelper.DANGER_ON, danger_on);
- provider_API_command.putExtra(ConfigHelper.downloadNewProviderDotJSON, method_and_parameters);
- provider_API_command.putExtra("receiver", providerAPI_result_receiver);
+ provider_API_command.putExtra(ConfigHelper.DOWNLOAD_NEW_PROVIDER_DOTJSON, method_and_parameters);
+ provider_API_command.putExtra(ConfigHelper.RECEIVER_KEY, providerAPI_result_receiver);
startService(provider_API_command);
}
+
+ /**
+ * Asks ProviderAPI to download a new provider.json file
+ * @param provider_name
+ * @param provider_json_url
+ * @param danger_on tells if HTTPS client should bypass certificate errors
+ */
+ public void updateProviderDotJson(String provider_name, String provider_json_url, boolean danger_on) {
+ Intent provider_API_command = new Intent(this, ProviderAPI.class);
- @Override
- public void onReceiveResult(int resultCode, Bundle resultData) {
- if(resultCode == ConfigHelper.CUSTOM_PROVIDER_ADDED){
- ProviderListFragment providerList = new ProviderListFragment();
+ Bundle method_and_parameters = new Bundle();
+ method_and_parameters.putString(ConfigHelper.PROVIDER_NAME, provider_name);
+ method_and_parameters.putString(ConfigHelper.PROVIDER_JSON_URL, provider_json_url);
+ method_and_parameters.putBoolean(ConfigHelper.DANGER_ON, danger_on);
- FragmentManager fragmentManager = getFragmentManager();
- fragmentManager.beginTransaction()
- .replace(R.id.configuration_wizard_layout, providerList, "providerlist")
- .commit();
- } - else if(resultCode == ConfigHelper.CORRECTLY_DOWNLOADED_JSON_FILES) {
- setResult(RESULT_OK);
- finish();
- }
- else if(resultCode == ConfigHelper.INCORRECTLY_DOWNLOADED_JSON_FILES) {
- setResult(RESULT_CANCELED);
- Toast.makeText(getApplicationContext(), "You have not entered a LEAP provider URL", Toast.LENGTH_LONG).show(); - }
+ provider_API_command.putExtra(ConfigHelper.UPDATE_PROVIDER_DOTJSON, method_and_parameters);
+ provider_API_command.putExtra(ConfigHelper.RECEIVER_KEY, providerAPI_result_receiver);
+
+ startService(provider_API_command);
}
}
diff --git a/src/se/leap/leapclient/Dashboard.java b/src/se/leap/leapclient/Dashboard.java index dce99b1e..dd8ee335 100644 --- a/src/se/leap/leapclient/Dashboard.java +++ b/src/se/leap/leapclient/Dashboard.java @@ -1,44 +1,66 @@ package se.leap.leapclient; +import org.apache.http.cookie.Cookie; +import org.apache.http.impl.cookie.BasicClientCookie; +import org.json.JSONException; +import org.json.JSONObject; + +import se.leap.leapclient.ProviderAPIResultReceiver.Receiver; +import se.leap.leapclient.R; import se.leap.openvpn.AboutFragment; import se.leap.openvpn.MainActivity; import android.app.Activity; +import android.app.AlertDialog; +import android.app.DialogFragment; import android.app.Fragment; 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.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.view.ViewStub; import android.widget.CompoundButton; import android.widget.Switch; import android.widget.TextView; +import android.widget.Toast; -public class Dashboard extends Activity { +public class Dashboard extends Activity implements LogInDialog.LogInDialogInterface, Receiver { protected static final int CONFIGURE_LEAP = 0; + private static Context app; private static SharedPreferences preferences; private static Provider provider; private TextView providerNameTV; private TextView eipTypeTV; + public ProviderAPIResultReceiver providerAPI_result_receiver; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + app = this; + setContentView(R.layout.client_dashboard); preferences = getSharedPreferences(ConfigHelper.PREFERENCES_KEY,MODE_PRIVATE); - + if(ConfigHelper.shared_preferences == null) + ConfigHelper.setSharedPreferences(preferences); + // Check if we have preferences, run configuration wizard if not // TODO We should do a better check for config that this! - if (!preferences.contains("provider") ) - startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); - else + if (preferences.contains("provider") && preferences.getString(ConfigHelper.PROVIDER_KEY, null) != null) buildDashboard(); + else + startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); } @Override @@ -50,8 +72,26 @@ public class Dashboard extends Activity { buildDashboard(); } else { - // Something went wrong... TODO figure out what - // TODO Error dialog + // Something went wrong in configuration + 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(ConfigHelper.PREFERENCES_KEY, MODE_PRIVATE).edit(); + prefsEdit.remove(ConfigHelper.PROVIDER_KEY).commit(); + finish(); + } + }); } } } @@ -73,7 +113,7 @@ public class Dashboard extends Activity { private void serviceItemEIP() { // FIXME Provider service (eip/openvpn) - View eipOverview = ((ViewStub) findViewById(R.id.eipOverviewStub)).inflate(); + ((ViewStub) findViewById(R.id.eipOverviewStub)).inflate(); // Set our EIP type title eipTypeTV = (TextView) findViewById(R.id.eipType); @@ -101,6 +141,23 @@ public class Dashboard extends Activity { } @Override + public boolean onPrepareOptionsMenu(Menu menu) { + JSONObject provider_json; + try { + provider_json = ConfigHelper.getJsonFromSharedPref(ConfigHelper.PROVIDER_KEY); + JSONObject service_description = provider_json.getJSONObject(ConfigHelper.SERVICE_KEY); + if(service_description.getBoolean(ConfigHelper.ALLOW_REGISTRATION_KEY)) { + menu.findItem(R.id.login_button).setVisible(true); + menu.findItem(R.id.logout_button).setVisible(true); + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return true; + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.client_dashboard, menu); @@ -128,6 +185,13 @@ public class Dashboard extends Activity { intent = new Intent(this,MainActivity.class); startActivity(intent); return true; + case R.id.login_button: + View view = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0); + logInDialog(view); + return true; + case R.id.logout_button: + logOut(); + return true; default: return super.onOptionsItemSelected(item); } @@ -139,4 +203,124 @@ public class Dashboard extends Activity { // TODO Expand the one line overview item to show some details } + @Override + public void authenticate(String username, String password) { + providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); + providerAPI_result_receiver.setReceiver(this); + + Intent provider_API_command = new Intent(this, ProviderAPI.class); + + Bundle method_and_parameters = new Bundle(); + method_and_parameters.putString(ConfigHelper.USERNAME_KEY, username); + method_and_parameters.putString(ConfigHelper.PASSWORD_KEY, password); + + JSONObject provider_json; + try { + provider_json = new JSONObject(preferences.getString(ConfigHelper.PROVIDER_KEY, "")); + method_and_parameters.putString(ConfigHelper.API_URL_KEY, provider_json.getString(ConfigHelper.API_URL_KEY) + "/" + provider_json.getString(ConfigHelper.API_VERSION_KEY)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + provider_API_command.putExtra(ConfigHelper.SRP_AUTH, method_and_parameters); + provider_API_command.putExtra(ConfigHelper.RECEIVER_KEY, providerAPI_result_receiver); + + startService(provider_API_command); + } + + /** + * 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 method_and_parameters = new Bundle(); + + JSONObject provider_json; + try { + provider_json = new JSONObject(preferences.getString(ConfigHelper.PROVIDER_KEY, "")); + method_and_parameters.putString(ConfigHelper.API_URL_KEY, provider_json.getString(ConfigHelper.API_URL_KEY) + "/" + provider_json.getString(ConfigHelper.API_VERSION_KEY)); + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + provider_API_command.putExtra(ConfigHelper.LOG_OUT, method_and_parameters); + provider_API_command.putExtra(ConfigHelper.RECEIVER_KEY, providerAPI_result_receiver); + + startService(provider_API_command); + } + + /** + * Shows the log in dialog. + * @param view from which the dialog is created. + */ + public void logInDialog(View view) { + FragmentTransaction fragment_transaction = getFragmentManager().beginTransaction(); + Fragment previous_log_in_dialog = getFragmentManager().findFragmentByTag(ConfigHelper.LOG_IN_DIALOG); + if (previous_log_in_dialog != null) { + fragment_transaction.remove(previous_log_in_dialog); + } + fragment_transaction.addToBackStack(null); + + DialogFragment newFragment = LogInDialog.newInstance(); + newFragment.show(fragment_transaction, ConfigHelper.LOG_IN_DIALOG); + } + + /** + * 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 method_and_parameters = new Bundle(); + method_and_parameters.putString(ConfigHelper.TYPE_OF_CERTIFICATE, ConfigHelper.AUTHED_CERTIFICATE); + method_and_parameters.putString(ConfigHelper.SESSION_ID_COOKIE_KEY, session_id.getName()); + method_and_parameters.putString(ConfigHelper.SESSION_ID_KEY, session_id.getValue()); + + provider_API_command.putExtra(ConfigHelper.DOWNLOAD_CERTIFICATE, method_and_parameters); + provider_API_command.putExtra(ConfigHelper.RECEIVER_KEY, providerAPI_result_receiver); + + startService(provider_API_command); + } + + @Override + public void onReceiveResult(int resultCode, Bundle resultData) { + if(resultCode == ConfigHelper.SRP_AUTHENTICATION_SUCCESSFUL){ + String session_id_cookie_key = resultData.getString(ConfigHelper.SESSION_ID_COOKIE_KEY); + String session_id_string = resultData.getString(ConfigHelper.SESSION_ID_KEY); + setResult(RESULT_OK); + Toast.makeText(getApplicationContext(), R.string.succesful_authentication_message, Toast.LENGTH_LONG).show(); + + Cookie session_id = new BasicClientCookie(session_id_cookie_key, session_id_string); + downloadAuthedUserCertificate(session_id); + } else if(resultCode == ConfigHelper.SRP_AUTHENTICATION_FAILED) { + setResult(RESULT_CANCELED); + Toast.makeText(getApplicationContext(), R.string.authentication_failed_message, Toast.LENGTH_LONG).show(); + } else if(resultCode == ConfigHelper.LOGOUT_SUCCESSFUL) { + setResult(RESULT_OK); + Toast.makeText(getApplicationContext(), R.string.successful_log_out_message, Toast.LENGTH_LONG).show(); + } else if(resultCode == ConfigHelper.LOGOUT_FAILED) { + setResult(RESULT_CANCELED); + Toast.makeText(getApplicationContext(), R.string.log_out_failed_message, Toast.LENGTH_LONG).show(); + } else if(resultCode == ConfigHelper.CORRECTLY_DOWNLOADED_CERTIFICATE) { + setResult(RESULT_CANCELED); + Toast.makeText(getApplicationContext(), R.string.successful_authed_cert_downloaded_message, Toast.LENGTH_LONG).show(); + } else if(resultCode == ConfigHelper.INCORRECTLY_DOWNLOADED_CERTIFICATE) { + setResult(RESULT_CANCELED); + Toast.makeText(getApplicationContext(), R.string.authed_cert_download_failed_message, Toast.LENGTH_LONG).show(); + } + } + + // Used for getting Context when outside of a class extending Context + public static Context getAppContext() { + return app; + } } diff --git a/src/se/leap/leapclient/LeapHttpClient.java b/src/se/leap/leapclient/LeapHttpClient.java index d1908c34..42f9a523 100644 --- a/src/se/leap/leapclient/LeapHttpClient.java +++ b/src/se/leap/leapclient/LeapHttpClient.java @@ -9,50 +9,63 @@ 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 { final Context context; - + private static LeapHttpClient client; - public LeapHttpClient(Context context) { - this.context = context; - } - - @Override - protected ClientConnectionManager createClientConnectionManager() { - SchemeRegistry registry = new SchemeRegistry(); - registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); - // Register for port 443 our SSLSocketFactory with our keystore - // to the ConnectionManager - registry.register(new Scheme("https", newSslSocketFactory(), 443)); - return new SingleClientConnManager(getParams(), registry); - } - - private SSLSocketFactory newSslSocketFactory() { - try { - // Get an instance of the Bouncy Castle KeyStore format - KeyStore trusted = ConfigHelper.getKeystore(); - - // Pass the keystore to the SSLSocketFactory. The factory is responsible - // for the verification of the server certificate. - SSLSocketFactory sf = new SSLSocketFactory(trusted); - - // Hostname verification from certificate - // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506 - sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - - return sf; - } catch (Exception e) { - throw new AssertionError(e); - } - } - - public static LeapHttpClient getInstance(Context context) { - if(client == null) - client = new LeapHttpClient(context); - return 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(Context context) { + if(client == null) { + client = new LeapHttpClient(context); + String cert_string = ConfigHelper.getStringFromSharedPref(ConfigHelper.MAIN_CERT_KEY); + if(cert_string != null) { + ConfigHelper.addTrustedCertificate("recovered_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. + * + * Sets hostname verifier to allow all hostname verifier. + * @return + */ + private SSLSocketFactory newSslSocketFactory() { + try { + KeyStore trusted = ConfigHelper.getKeystore(); + SSLSocketFactory sf = new SSLSocketFactory(trusted); + sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + + return sf; + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public LeapHttpClient(Context context) { + this.context = context; + } } diff --git a/src/se/leap/leapclient/LeapSRPSession.java b/src/se/leap/leapclient/LeapSRPSession.java index abdf6b2c..d21ccffe 100644 --- a/src/se/leap/leapclient/LeapSRPSession.java +++ b/src/se/leap/leapclient/LeapSRPSession.java @@ -4,23 +4,33 @@ 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.Util; -import org.jboss.security.srp.SRPClientSession; import org.jboss.security.srp.SRPParameters; -import org.jboss.security.srp.SRPPermission; +/** + * 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 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 */ @@ -34,7 +44,7 @@ public class LeapSRPSession { @param password, the user clear text password @param params, the SRP parameters for the session */ - public LeapSRPSession(String username, char[] password, SRPParameters params) + public LeapSRPSession(String username, String password, SRPParameters params) { this(username, password, params, null); } @@ -44,23 +54,24 @@ public class LeapSRPSession { @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. This must be - 8 bytes in length. + @param abytes, the random exponent used in the A public key */ - public LeapSRPSession(String username, char[] password, SRPParameters params, + public LeapSRPSession(String username, String password, SRPParameters params, byte[] abytes) { - try { - // Initialize the secure random number and message digests - Util.init(); - } - catch(NoSuchAlgorithmException e) { - } - this.params = params; this.g = new BigInteger(1, params.g); - byte[] N_bytes = Util.trim(params.N); + 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? @@ -70,42 +81,11 @@ public class LeapSRPSession { */ this.a = new BigInteger(abytes); } - - // Calculate x = H(s | H(U | ':' | password)) - byte[] salt_bytes = Util.trim(params.s); - byte[] xb = calculatePasswordHash(username, password, salt_bytes); - this.x = new BigInteger(1, xb); - - // Calculate v = kg^x mod N - String k_string = "bf66c44a428916cad64aa7c679f3fd897ad4c375e9bbb4cbf2f5de241d618ef0"; - this.v = calculateV(k_string); - //String v_string = v.toString(16); + else + A_LEN = 64; serverHash = newDigest(); clientHash = newDigest(); - - // 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, digest_of_g.length); - //String hxg_string = new BigInteger(1, xor_digest).toString(16); - clientHash.update(xor_digest); - - // clientHash = H(N) xor H(g) | H(U) - byte[] username_digest = newDigest().digest(Util.trim(username.getBytes())); - username_digest = Util.trim(username_digest); - //String username_digest_string = new BigInteger(1, username_digest).toString(16); - clientHash.update(username_digest); - - // clientHash = H(N) xor H(g) | H(U) | s - //String salt_string = new BigInteger(1, salt_bytes).toString(16); - clientHash.update(salt_bytes); - - K = null; } /** @@ -115,49 +95,38 @@ public class LeapSRPSession { * @param salt the salt of the user * @return x */ - public byte[] calculatePasswordHash(String username, char[] password, byte[] salt) + 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 UTF-8 + // 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 = Util.trim(username.getBytes("UTF-8")); - colon = Util.trim(":".getBytes("UTF-8")); + 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 = Util.trim(username.getBytes()); - colon = Util.trim(":".getBytes()); - } - - byte[] passBytes = new byte[2*password.length]; - int passBytesLength = 0; - for(int p = 0; p < password.length; p++) { - int c = (password[p] & 0x00FFFF); - // The low byte of the char - byte b0 = (byte) (c & 0x0000FF); - // The high byte of the char - byte b1 = (byte) ((c & 0x00FF00) >> 8); - passBytes[passBytesLength ++] = b0; - // Only encode the high byte if c is a multi-byte char - if( c > 255 ) - passBytes[passBytesLength ++] = b1; + 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(passBytes, 0, passBytesLength); + x_digest.update(password_bytes); byte[] h = x_digest.digest(); - //h = Util.trim(h); x_digest.reset(); x_digest.update(salt); x_digest.update(h); - byte[] x_digest_bytes = Util.trim(x_digest.digest()); + byte[] x_digest_bytes = x_digest.digest(); return x_digest_bytes; } @@ -169,14 +138,22 @@ public class LeapSRPSession { */ private BigInteger calculateV(String k_string) { BigInteger k = new BigInteger(k_string, 16); - return k.multiply(g.modPow(x, N)); // g^x % N + BigInteger v = k.multiply(g.modPow(x, N)); // g^x % N + return v; } - public byte[] xor(byte[] b1, byte[] b2, int length) + /** + * 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 Util.trim(xor_digest); + return ConfigHelper.trim(xor_digest); } /** @@ -190,18 +167,11 @@ public class LeapSRPSession { if( a == null ) { BigInteger one = BigInteger.ONE; do { - a = new BigInteger(A_LEN, Util.getPRNG()); + a = new BigInteger(A_LEN, pseudoRng); } while(a.compareTo(one) <= 0); } A = g.modPow(a, N); - Abytes = Util.trim(A.toByteArray()); - //String Abytes_string = new BigInteger(1, Abytes).toString(16); - - // clientHash = H(N) xor H(g) | H(U) | A - clientHash.update(Abytes); - - // serverHash = A - serverHash.update(Abytes); + Abytes = ConfigHelper.trim(A.toByteArray()); } return Abytes; } @@ -209,35 +179,68 @@ public class LeapSRPSession { /** * 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[] Bbytes) throws NoSuchAlgorithmException { + public byte[] response(byte[] salt_bytes, byte[] Bbytes) throws NoSuchAlgorithmException { + // Calculate x = H(s | H(U | ':' | password)) + 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 = Util.trim(Bbytes); - //String Bbytes_string = new BigInteger(1, Bbytes).toString(16); + Bbytes = ConfigHelper.trim(Bbytes); clientHash.update(Bbytes); // Calculate S = (B - kg^x) ^ (a + u * x) % N BigInteger S = calculateS(Bbytes); - byte[] S_bytes = Util.trim(S.toByteArray()); - //String S_bytes_string = new BigInteger(1, S_bytes).toString(16); + byte[] S_bytes = ConfigHelper.trim(S.toByteArray()); // K = SessionHash(S) String hash_algorithm = params.hashAlgorithm; MessageDigest sessionDigest = MessageDigest.getInstance(hash_algorithm); - K = sessionDigest.digest(S_bytes); - //K = Util.trim(K); - //String K_bytes_string = new BigInteger(1, K).toString(16); + K = ConfigHelper.trim(sessionDigest.digest(S_bytes)); // clientHash = H(N) xor H(g) | H(U) | A | B | K clientHash.update(K); - byte[] M1 = clientHash.digest(); + + byte[] M1 = ConfigHelper.trim(clientHash.digest()); // serverHash = Astr + M + K + serverHash.update(Abytes); serverHash.update(M1); serverHash.update(K); + return M1; } @@ -247,19 +250,16 @@ public class LeapSRPSession { * @return the parameter S */ private BigInteger calculateS(byte[] Bbytes) { - byte[] Abytes = Util.trim(A.toByteArray()); + byte[] Abytes = ConfigHelper.trim(A.toByteArray()); + Bbytes = ConfigHelper.trim(Bbytes); byte[] u_bytes = getU(Abytes, Bbytes); - //ub = Util.trim(ub); BigInteger B = new BigInteger(1, Bbytes); BigInteger u = new BigInteger(1, u_bytes); - //String u_string = u.toString(16); BigInteger B_minus_v = B.subtract(v); BigInteger a_ux = a.add(u.multiply(x)); - //String a_ux_string = a_ux.toString(16); BigInteger S = B_minus_v.modPow(a_ux, N); - return S; } @@ -271,9 +271,10 @@ public class LeapSRPSession { */ public byte[] getU(byte[] Abytes, byte[] Bbytes) { MessageDigest u_digest = newDigest(); - u_digest.update(Abytes); - u_digest.update(Bbytes); - return new BigInteger(1, u_digest.digest()).toByteArray(); + 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()); } /** @@ -283,22 +284,12 @@ public class LeapSRPSession { public boolean verify(byte[] M2) { // M2 = H(A | M1 | K) - byte[] myM2 = serverHash.digest(); + M2 = ConfigHelper.trim(M2); + byte[] myM2 = ConfigHelper.trim(serverHash.digest()); boolean valid = Arrays.equals(M2, myM2); return valid; } - /** Returns the negotiated session K, K = SHA_Interleave(S) - @return the private session K byte[] - @throws SecurityException - if the current thread does not have an - getSessionKey SRPPermission. - */ - public byte[] getSessionKey() throws SecurityException - { - return K; - } - - /** * @return a new SHA-256 digest. */ diff --git a/src/se/leap/leapclient/LogInDialog.java b/src/se/leap/leapclient/LogInDialog.java new file mode 100644 index 00000000..dcb92d89 --- /dev/null +++ b/src/se/leap/leapclient/LogInDialog.java @@ -0,0 +1,100 @@ +package se.leap.leapclient; + +import se.leap.leapclient.R; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.Toast; + +/** + * 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 { + + 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 EditText username_field = (EditText)log_in_dialog_view.findViewById(R.id.username_entered); + final EditText password_field = (EditText)log_in_dialog_view.findViewById(R.id.password_entered); + + builder.setView(log_in_dialog_view) + .setPositiveButton(R.string.log_in_button, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + String username = username_field.getText().toString().trim(); + String password = password_field.getText().toString().trim(); + if(wellFormedPassword(password)) { + interface_with_Dashboard.authenticate(username, password); + } else { + password_field.setText(""); + Toast.makeText(getActivity().getApplicationContext(), R.string.not_valid_password_message, Toast.LENGTH_LONG).show(); + } + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + return builder.create(); + } + + /** + * 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; + } + + /** + * 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); + } + + 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 NoticeDialogListener"); + } + } +} diff --git a/src/se/leap/leapclient/NewProviderDialog.java b/src/se/leap/leapclient/NewProviderDialog.java index 88e4711c..678e805f 100644 --- a/src/se/leap/leapclient/NewProviderDialog.java +++ b/src/se/leap/leapclient/NewProviderDialog.java @@ -1,5 +1,6 @@ package se.leap.leapclient; +import se.leap.leapclient.R; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -8,17 +9,27 @@ import android.content.DialogInterface; 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 { public interface NewProviderDialogInterface { - public void saveProvider(String url_provider); + public void saveProvider(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; @@ -27,33 +38,37 @@ public class NewProviderDialog extends DialogFragment { @Override public void onAttach(Activity activity) { super.onAttach(activity); - // Verify that the host activity implements the callback interface try { - // Instantiate the NoticeDialogListener so we can send events to the host interface_with_ConfigurationWizard = (NewProviderDialogInterface) activity; } catch (ClassCastException e) { - // The activity doesn't implement the interface, throw exception 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); + final CheckBox danger_checkbox = (CheckBox)new_provider_dialog_view.findViewById(R.id.danger_checkbox); + 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://")) { + entered_url = "https://".concat(entered_url); + } + boolean danger_on = danger_checkbox.isChecked(); if(validURL(entered_url)) { - interface_with_ConfigurationWizard.saveProvider(entered_url); - Toast.makeText(getActivity().getApplicationContext(), "It seems your URL is well formed", Toast.LENGTH_LONG).show(); + interface_with_ConfigurationWizard.saveProvider(entered_url, danger_on); + Toast.makeText(getActivity().getApplicationContext(), R.string.valid_url_entered, Toast.LENGTH_LONG).show(); } else { url_input_field.setText(""); - Toast.makeText(getActivity().getApplicationContext(), "It seems your URL is not well formed", Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity().getApplicationContext(), R.string.not_valid_password_message, Toast.LENGTH_LONG).show(); } } }) @@ -66,6 +81,11 @@ public class NewProviderDialog extends DialogFragment { 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(); } diff --git a/src/se/leap/leapclient/Provider.java b/src/se/leap/leapclient/Provider.java index 4235acfd..50bd2925 100644 --- a/src/se/leap/leapclient/Provider.java +++ b/src/se/leap/leapclient/Provider.java @@ -11,6 +11,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import android.content.Context; import android.app.Activity; import android.content.SharedPreferences; @@ -18,7 +19,7 @@ import android.content.SharedPreferences; * @author Sean Leonard <meanderingcode@aetherislands.net> * */ -final class Provider implements Serializable { +public final class Provider implements Serializable { private static final long serialVersionUID = 6003835972151761353L; @@ -67,7 +68,7 @@ final class Provider implements Serializable { //preferences = context.getgetPreferences(0); // 0 == MODE_PRIVATE, but we don't extend Android's classes... // Load SharedPreferences - preferences = activity.getSharedPreferences(ConfigHelper.PREFERENCES_KEY,0); // We don't get MODE_PRIVATE by importing SharedPreferences; i guess it's in Activity? + preferences = activity.getSharedPreferences(ConfigHelper.PREFERENCES_KEY,Context.MODE_PRIVATE); // Inflate our provider.json data try { definition = new JSONObject( preferences.getString("provider", "") ); diff --git a/src/se/leap/leapclient/ProviderAPI.java b/src/se/leap/leapclient/ProviderAPI.java index 4ffd2762..00d7d820 100644 --- a/src/se/leap/leapclient/ProviderAPI.java +++ b/src/se/leap/leapclient/ProviderAPI.java @@ -1,20 +1,41 @@ package se.leap.leapclient; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.math.BigInteger; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.util.List; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.HttpCookie; import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URISyntaxException; import java.net.URL; -import java.net.UnknownHostException; +import java.net.URLConnection; import java.util.Scanner; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.protocol.ClientContext; import org.apache.http.cookie.Cookie; import org.apache.http.impl.client.DefaultHttpClient; @@ -30,8 +51,18 @@ import android.app.IntentService; import android.content.Intent; import android.os.Bundle; 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 fromt he Internet, so it operates in the background. + * + * @author parmegv + * @author MeanderingCode + * + */ public class ProviderAPI extends IntentService { public ProviderAPI() { @@ -44,240 +75,533 @@ public class ProviderAPI extends IntentService { final ResultReceiver receiver = task_for.getParcelableExtra("receiver"); Bundle task; - if((task = task_for.getBundleExtra(ConfigHelper.downloadJsonFilesBundleExtra)) != null) { - if(!downloadJsonFiles(task)) + if((task = task_for.getBundleExtra(ConfigHelper.DOWNLOAD_JSON_FILES_BUNDLE_EXTRA)) != null) { + if(!downloadJsonFiles(task)) { receiver.send(ConfigHelper.INCORRECTLY_DOWNLOADED_JSON_FILES, Bundle.EMPTY); - else + } else { receiver.send(ConfigHelper.CORRECTLY_DOWNLOADED_JSON_FILES, Bundle.EMPTY); + } } - else if ((task = task_for.getBundleExtra(ConfigHelper.downloadNewProviderDotJSON)) != null) { - if(downloadNewProviderDotJSON(task)) - receiver.send(ConfigHelper.CORRECTLY_DOWNLOADED_JSON_FILES, Bundle.EMPTY); - else + else if ((task = task_for.getBundleExtra(ConfigHelper.UPDATE_PROVIDER_DOTJSON)) != null) { + Bundle result = updateProviderDotJSON(task); + if(result.getBoolean(ConfigHelper.RESULT_KEY)) { + receiver.send(ConfigHelper.CORRECTLY_UPDATED_PROVIDER_DOT_JSON, result); + } else { + receiver.send(ConfigHelper.INCORRECTLY_UPDATED_PROVIDER_DOT_JSON, Bundle.EMPTY); + } + } + else if ((task = task_for.getBundleExtra(ConfigHelper.DOWNLOAD_NEW_PROVIDER_DOTJSON)) != null) { + if(downloadNewProviderDotJSON(task)) { + receiver.send(ConfigHelper.CUSTOM_PROVIDER_ADDED, Bundle.EMPTY); + } else { receiver.send(ConfigHelper.INCORRECTLY_DOWNLOADED_JSON_FILES, Bundle.EMPTY); + } + } + else if ((task = task_for.getBundleExtra(ConfigHelper.SRP_AUTH)) != null) { + Bundle session_id_bundle = authenticateBySRP(task); + if(session_id_bundle.getBoolean(ConfigHelper.RESULT_KEY)) { + receiver.send(ConfigHelper.SRP_AUTHENTICATION_SUCCESSFUL, session_id_bundle); + } else { + receiver.send(ConfigHelper.SRP_AUTHENTICATION_FAILED, Bundle.EMPTY); + } + } + else if ((task = task_for.getBundleExtra(ConfigHelper.LOG_OUT)) != null) { + if(logOut(task)) { + receiver.send(ConfigHelper.LOGOUT_SUCCESSFUL, Bundle.EMPTY); + } else { + receiver.send(ConfigHelper.LOGOUT_FAILED, Bundle.EMPTY); + } + } + else if ((task = task_for.getBundleExtra(ConfigHelper.DOWNLOAD_CERTIFICATE)) != null) { + if(getNewCert(task)) { + receiver.send(ConfigHelper.CORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); + } else { + receiver.send(ConfigHelper.INCORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); + } } } + /** + * Downloads the main cert and the eip-service.json files given through the task parameter + * @param task + * @return true if eip-service.json was parsed as a JSON object correctly. + */ private boolean downloadJsonFiles(Bundle task) { - String cert_url = (String) task.get(ConfigHelper.cert_key); - String eip_service_json_url = (String) task.get(ConfigHelper.eip_service_key); + String cert_url = task.getString(ConfigHelper.MAIN_CERT_KEY); + String eip_service_json_url = task.getString(ConfigHelper.EIP_SERVICE_KEY); + boolean danger_on = task.getBoolean(ConfigHelper.DANGER_ON); try { - String cert_string = getStringFromProvider(cert_url); - JSONObject cert_json = new JSONObject("{ \"certificate\" : \"" + cert_string + "\"}"); - ConfigHelper.saveSharedPref(ConfigHelper.cert_key, cert_json); - JSONObject eip_service_json = getJSONFromProvider(eip_service_json_url); - ConfigHelper.saveSharedPref(ConfigHelper.eip_service_key, eip_service_json); + String cert_string = getStringFromProvider(cert_url, danger_on); + ConfigHelper.saveSharedPref(ConfigHelper.MAIN_CERT_KEY, cert_string); + JSONObject eip_service_json = getJSONFromProvider(eip_service_json_url, danger_on); + ConfigHelper.saveSharedPref(ConfigHelper.EIP_SERVICE_KEY, eip_service_json); return true; - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; } catch (JSONException e) { - ConfigHelper.rescueJSONException(e); - return false; - } catch(Exception e) { - e.printStackTrace(); return false; } } - - private boolean registerWithSRP(Bundle task) { - String username = (String) task.get(ConfigHelper.username_key); - String password = (String) task.get(ConfigHelper.password_key); - String authentication_server = (String) task.get(ConfigHelper.srp_server_url_key); - - BigInteger ng_1024 = new BigInteger(ConfigHelper.NG_1024, 16); - BigInteger salt = ng_1024.probablePrime(1024, null); - byte[] salt_in_bytes = salt.toByteArray(); - - return false; - } - private boolean authenticateBySRP(Bundle task) { - String username = (String) task.get(ConfigHelper.username_key); - String password = (String) task.get(ConfigHelper.password_key); - String authentication_server = (String) task.get(ConfigHelper.srp_server_url_key); + /** + * 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 ConfigHelper.RESULT_KEY, and which is true if authentication was successful. + */ + private Bundle authenticateBySRP(Bundle task) { + Bundle session_id_bundle = new Bundle(); - String salt = "abcd"; + String username = (String) task.get(ConfigHelper.USERNAME_KEY); + String password = (String) task.get(ConfigHelper.PASSWORD_KEY); + String authentication_server = (String) task.get(ConfigHelper.API_URL_KEY); - SRPParameters params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - //SRPClientSession client = new SRPClientSession(username, password.toCharArray(), params); - LeapSRPSession client = new LeapSRPSession(username, password.toCharArray(), params); + 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(); try { - JSONObject saltAndB = sendAToSRPServer(authentication_server, username, new BigInteger(A).toString(16)); + JSONObject saltAndB = sendAToSRPServer(authentication_server, username, new BigInteger(1, A).toString(16)); if(saltAndB.length() > 0) { - byte[] B = saltAndB.getString("B").getBytes(); - salt = saltAndB.getString("salt"); - params = new SRPParameters(new BigInteger(ConfigHelper.NG_1024, 16).toByteArray(), new BigInteger("2").toByteArray(), new BigInteger(salt, 16).toByteArray(), "SHA-256"); - //client = new SRPClientSession(username, password.toCharArray(), params); - client = new LeapSRPSession(username, password.toCharArray(), params); - A = client.exponential(); - saltAndB = sendAToSRPServer(authentication_server, username, new BigInteger(A).toString(16)); - String Bhex = saltAndB.getString("B"); - byte[] M1 = client.response(new BigInteger(Bhex, 16).toByteArray()); - byte[] M2 = sendM1ToSRPServer(authentication_server, username, M1); - if( client.verify(M2) == false ) - throw new SecurityException("Failed to validate server reply"); - return true; + String salt = saltAndB.getString(ConfigHelper.SALT_KEY); + byte[] Bbytes = new BigInteger(saltAndB.getString("B"), 16).toByteArray(); + byte[] M1 = client.response(new BigInteger(salt, 16).toByteArray(), Bbytes); + JSONObject session_idAndM2 = sendM1ToSRPServer(authentication_server, username, M1); + if( client.verify((byte[])session_idAndM2.get("M2")) == false ) { + session_id_bundle.putBoolean(ConfigHelper.RESULT_KEY, false); + } else { + session_id_bundle.putBoolean(ConfigHelper.RESULT_KEY, true); + session_id_bundle.putString(ConfigHelper.SESSION_ID_KEY, session_idAndM2.getString(ConfigHelper.SESSION_ID_KEY)); + session_id_bundle.putString(ConfigHelper.SESSION_ID_COOKIE_KEY, session_idAndM2.getString(ConfigHelper.SESSION_ID_COOKIE_KEY)); + } + } else { + session_id_bundle.putBoolean(ConfigHelper.RESULT_KEY, false); } - else return false; - } catch (ClientProtocolException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - return false; - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - return false; - } catch (JSONException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - return false; + } catch (ClientProtocolException e) { + session_id_bundle.putBoolean(ConfigHelper.RESULT_KEY, false); + } catch (IOException e) { + session_id_bundle.putBoolean(ConfigHelper.RESULT_KEY, false); + } catch (JSONException e) { + session_id_bundle.putBoolean(ConfigHelper.RESULT_KEY, false); } catch (NoSuchAlgorithmException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; + session_id_bundle.putBoolean(ConfigHelper.RESULT_KEY, false); } + + return session_id_bundle; } + /** + * 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 + */ private JSONObject sendAToSRPServer(String server_url, String username, String clientA) throws ClientProtocolException, IOException, JSONException { - DefaultHttpClient client = LeapHttpClient.getInstance(getApplicationContext()); - String parameter_chain = "A" + "=" + clientA + "&" + "login" + "=" + username; - HttpPost post = new HttpPost(server_url + "/sessions.json" + "?" + parameter_chain); + 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 + */ + private JSONObject sendM1ToSRPServer(String server_url, String username, byte[] m1) throws ClientProtocolException, IOException, JSONException { + HttpPut put = new HttpPut(server_url + "/sessions/" + username +".json" + "?" + "client_auth" + "=" + new BigInteger(1, ConfigHelper.trim(m1)).toString(16)); + JSONObject json_response = sendToServer(put); + + JSONObject session_idAndM2 = new JSONObject(); + if(json_response.length() > 0) { + byte[] M2_not_trimmed = new BigInteger(json_response.getString(ConfigHelper.M2_KEY), 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(ConfigHelper.M2_KEY, ConfigHelper.trim(M2_not_trimmed)); + } + return session_idAndM2; + } - HttpResponse getResponse = client.execute(post); + /** + * Executes an HTTP request expecting a JSON response. + * @param request + * @return response from authentication server + * @throws ClientProtocolException + * @throws IOException + * @throws JSONException + */ + private JSONObject sendToServer(HttpUriRequest request) throws ClientProtocolException, IOException, JSONException { + DefaultHttpClient client = LeapHttpClient.getInstance(getApplicationContext()); + HttpContext localContext = new BasicHttpContext(); + localContext.setAttribute(ClientContext.COOKIE_STORE, client.getCookieStore()); + + HttpResponse getResponse = client.execute(request, localContext); HttpEntity responseEntity = getResponse.getEntity(); String plain_response = new Scanner(responseEntity.getContent()).useDelimiter("\\A").next(); JSONObject json_response = new JSONObject(plain_response); - if(!json_response.isNull("errors") || json_response.has("errors")) { + if(!json_response.isNull(ConfigHelper.ERRORS_KEY) || json_response.has(ConfigHelper.ERRORS_KEY)) { return new JSONObject(); } - List<Cookie> cookies = client.getCookieStore().getCookies(); - if(!cookies.isEmpty()) { - String session_id = cookies.get(0).getValue(); - } + return json_response; } - private byte[] sendM1ToSRPServer(String server_url, String username, byte[] m1) throws ClientProtocolException, IOException, JSONException { - DefaultHttpClient client = LeapHttpClient.getInstance(getApplicationContext()); - String parameter_chain = "client_auth" + "=" + new BigInteger(m1).toString(16); - HttpPut put = new HttpPut(server_url + "/sessions/" + username +".json" + "?" + parameter_chain); - HttpContext localContext = new BasicHttpContext(); - localContext.setAttribute(ClientContext.COOKIE_STORE, client.getCookieStore()); + /** + * 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 ConfigHelper.RESULT_KEY, and which is true if the update was successful. + */ + private Bundle updateProviderDotJSON(Bundle task) { + Bundle result = new Bundle(); + boolean custom = task.getBoolean(ConfigHelper.CUSTOM); + boolean danger_on = task.getBoolean(ConfigHelper.DANGER_ON); + String provider_json_url = task.getString(ConfigHelper.PROVIDER_JSON_URL); + String provider_name = task.getString(ConfigHelper.PROVIDER_NAME); - HttpResponse getResponse = client.execute(put, localContext); - HttpEntity responseEntity = getResponse.getEntity(); - String plain_response = new Scanner(responseEntity.getContent()).useDelimiter("\\A").next(); - JSONObject json_response = new JSONObject(plain_response); - if(!json_response.isNull("errors") || json_response.has("errors")) { - return new byte[0]; + try { + JSONObject provider_json = getJSONFromProvider(provider_json_url, danger_on); + if(provider_json == null) { + result.putBoolean(ConfigHelper.RESULT_KEY, false); + } else { + ConfigHelper.saveSharedPref(ConfigHelper.ALLOWED_ANON, provider_json.getJSONObject(ConfigHelper.SERVICE_KEY).getBoolean(ConfigHelper.ALLOWED_ANON)); + + ProviderListContent.addItem(new ProviderItem(provider_name, provider_json_url, provider_json, custom, danger_on)); + result.putBoolean(ConfigHelper.RESULT_KEY, true); + result.putString(ConfigHelper.PROVIDER_KEY, provider_json.toString()); + result.putBoolean(ConfigHelper.DANGER_ON, danger_on); + } + } catch (JSONException e) { + result.putBoolean(ConfigHelper.RESULT_KEY, false); } - return json_response.getString("M2").getBytes(); + return result; } + /** + * Downloads a custom provider provider.json file + * @param task containing a boolean meaning if the user completely trusts this provider, and the provider main url entered in the new custom provider dialog. + * @return true if provider.json file was successfully parsed as a JSON object. + */ private boolean downloadNewProviderDotJSON(Bundle task) { boolean custom = true; - String provider_main_url = (String) task.get(ConfigHelper.provider_main_url); + boolean danger_on = task.getBoolean(ConfigHelper.DANGER_ON); + + String provider_main_url = (String) task.get(ConfigHelper.PROVIDER_MAIN_URL); String provider_name = provider_main_url.replaceFirst("http[s]?://", "").replaceFirst("\\/", "_"); - String provider_json_url = guessURL(provider_main_url); - JSONObject provider_json = null; + String provider_json_url = guessProviderDotJsonURL(provider_main_url); + + JSONObject provider_json; try { - provider_json = getJSONFromProvider(provider_json_url); - } catch (IOException e) { - // It could happen that an https site used a certificate not trusted. - provider_json = downloadNewProviderDotJsonWithoutCert(provider_json_url); + provider_json = getJSONFromProvider(provider_json_url, danger_on); + ProviderListContent.addItem(new ProviderItem(provider_name, provider_json_url, provider_json, custom, danger_on)); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); return false; } - if(provider_json == null) { - return false; - } else { - String filename = provider_name + "_provider.json".replaceFirst("__", "_"); - ConfigHelper.saveFile(filename, provider_json.toString()); - ConfigHelper.saveSharedPref(ConfigHelper.provider_key, provider_json); + return true; + } + + /** + * Tries to download whatever is pointed by the string_url. + * + * 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 getStringFromProvider(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); + json_file_content = new Scanner(url_connection.getInputStream()).useDelimiter("\\A").next(); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch(SocketTimeoutException e) { + return ""; + } catch (IOException e) { + // TODO SSLHandshakeException + // This means that we have not added ca.crt to the trusted certificates. + if(provider_url != null && danger_on) { + json_file_content = getStringFromProviderWithoutValidate(provider_url); + } + //json_file_content = downloadStringFromProviderWithCACertAdded(string_url); + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } - ProviderListContent.addItem(new ProviderItem(provider_name, ConfigHelper.openFileInputStream(filename), custom)); - return true; + return json_file_content; + } + + /** + * Tries to download a string from given url without verifying the hostname. + * + * If a IOException still occurs, it tries with another bypass method: getStringFromProviderWithCACertAdded. + * @param string_url + * @return an empty string if everything fails, the url content if not. + */ + private String getStringFromProviderWithoutValidate( + URL string_url) { + + String json_string = ""; + HostnameVerifier hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + try { + HttpsURLConnection urlConnection = + (HttpsURLConnection)string_url.openConnection(); + urlConnection.setHostnameVerifier(hostnameVerifier); + json_string = new Scanner(urlConnection.getInputStream()).useDelimiter("\\A").next(); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + json_string = getStringFromProviderWithCACertAdded(string_url); + //e.printStackTrace(); } + + return json_string; } - private boolean downloadJsonFilesBundleExtra(Bundle task) { - //TODO task only contains provider main url -> we need to infer cert_url, provider_name and eip_service_json_url from that. - String provider_main_url = (String) task.get(ConfigHelper.provider_main_url); - String provider_name = ConfigHelper.extractProviderName(provider_main_url); - String cert_url = (String) task.get(ConfigHelper.cert_key); - String eip_service_json_url = (String) task.get(ConfigHelper.eip_service_key); + /** + * Tries to download the contents of the provided url using main certificate from choosen provider. + * @param url + * @return an empty string if it fails, the url content if not. + */ + private String getStringFromProviderWithCACertAdded(URL url) { + String json_file_content = ""; + + // Load CAs from an InputStream + // (could be from a resource or ByteArrayInputStream or ...) + String cert_string = ConfigHelper.getStringFromSharedPref(ConfigHelper.MAIN_CERT_KEY); + if(cert_string.isEmpty()) { + cert_string = downloadCertificateWithoutTrusting(url.getProtocol() + "://" + url.getHost() + "/" + "ca.crt"); + ConfigHelper.saveSharedPref(ConfigHelper.MAIN_CERT_KEY, cert_string); + } + CertificateFactory cf; try { - //JSONObject provider_json = new JSONObject("{ \"provider\" : \"" + provider_name + "\"}"); - //ConfigHelper.saveSharedPref(ConfigHelper.provider_key, provider_json); - - /*String cert_string = getStringFromProvider(cert_url); - JSONObject cert_json = new JSONObject("{ \"certificate\" : \"" + cert_string + "\"}"); - ConfigHelper.saveSharedPref(ConfigHelper.cert_key, cert_json); - ConfigHelper.addTrustedCertificate(provider_name, cert_string);*/ - URL cacert = new URL(cert_url); - ConfigHelper.addTrustedCertificate(provider_name, cacert.openStream()); - JSONObject eip_service_json = getJSONFromProvider(eip_service_json_url); - ConfigHelper.saveSharedPref(ConfigHelper.eip_service_key, eip_service_json); - return true; + cf = CertificateFactory.getInstance("X.509"); + + cert_string = cert_string.replaceFirst("-----BEGIN CERTIFICATE-----", "").replaceFirst("-----END CERTIFICATE-----", "").trim(); + byte[] cert_bytes = Base64.decode(cert_string, Base64.DEFAULT); + InputStream caInput = new ByteArrayInputStream(cert_bytes); + java.security.cert.Certificate ca; + try { + ca = cf.generateCertificate(caInput); + System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); + } finally { + caInput.close(); + } + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca", ca); + + // 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); + + // Tell the URLConnection to use a SocketFactory from our SSLContext + HttpsURLConnection urlConnection = + (HttpsURLConnection)url.openConnection(); + urlConnection.setSSLSocketFactory(context.getSocketFactory()); + json_file_content = new Scanner(urlConnection.getInputStream()).useDelimiter("\\A").next(); + } catch (CertificateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } catch (IOException e) { - //TODO It could happen when the url is not valid. + // TODO Auto-generated catch block e.printStackTrace(); - return false; - } catch (JSONException e) { - ConfigHelper.rescueJSONException(e); - return false; - } catch(Exception e) { + } 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 false; } + return json_file_content; } + + /** + * Downloads the certificate from the parameter url bypassing self signed certificate SSL errors. + * @param certificate_url_string + * @return the certificate, as a string + */ + private String downloadCertificateWithoutTrusting(String certificate_url_string) { + + String cert_string = ""; + HostnameVerifier hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType) { + } + public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType) { + } + } + }; - private JSONObject downloadNewProviderDotJsonWithoutCert( - String provider_json_url) { - JSONObject provider_json = null; try { - URL provider_url = new URL(provider_json_url); - String provider_json_string = new Scanner(provider_url.openStream()).useDelimiter("\\A").next(); - provider_json = new JSONObject(provider_json_string); - } catch (MalformedURLException e1) { - e1.printStackTrace(); - } catch (UnknownHostException e1) { - e1.printStackTrace(); - } catch (IOException e1) { - e1.printStackTrace(); - } catch (JSONException e1) { - e1.printStackTrace(); + URL certificate_url = new URL(certificate_url_string); + HttpsURLConnection urlConnection = + (HttpsURLConnection)certificate_url.openConnection(); + urlConnection.setHostnameVerifier(hostnameVerifier); + + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + + urlConnection.setSSLSocketFactory(sc.getSocketFactory()); + + cert_string = new Scanner(urlConnection.getInputStream()).useDelimiter("\\A").next(); + + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // This should never happen + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (KeyManagementException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } - return provider_json; + + return cert_string; } - private String guessURL(String provider_main_url) { - return provider_main_url + "/provider.json"; + /** + * Downloads a JSON object from the given url. + * + * It first downloads the JSON object as a String, and then parses it to JSON object. + * @param json_url + * @param danger_on if the user completely trusts the certificate of the url address. + * @return + * @throws JSONException + */ + private JSONObject getJSONFromProvider(String json_url, boolean danger_on) throws JSONException { + String json_file_content = getStringFromProvider(json_url, danger_on); + return new JSONObject(json_file_content); } - private String getStringFromProvider(String string_url) throws IOException { - - String json_file_content = ""; - + /** + * Tries to guess the provider.json url given the main provider url. + * @param provider_main_url + * @return the guessed provider.json url + */ + private String guessProviderDotJsonURL(String provider_main_url) { + return provider_main_url + "/provider.json"; + } + + /** + * 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) { DefaultHttpClient client = LeapHttpClient.getInstance(getApplicationContext()); - HttpGet get = new HttpGet(string_url); - // Execute the GET call and obtain the response - HttpResponse getResponse = client.execute(get); - HttpEntity responseEntity = getResponse.getEntity(); - - json_file_content = new Scanner(responseEntity.getContent()).useDelimiter("\\A").next(); - - return json_file_content; + int session_id_index = 0; + //String delete_url = task.getString(ConfigHelper.srp_server_url_key) + "/sessions/" + client.getCookieStore().getCookies().get(0).getValue(); + try { + String delete_url = task.getString(ConfigHelper.API_URL_KEY) + "/logout" + "?authenticity_token=" + client.getCookieStore().getCookies().get(session_id_index).getValue(); + HttpDelete delete = new HttpDelete(delete_url); + HttpResponse getResponse = client.execute(delete); + HttpEntity responseEntity = getResponse.getEntity(); + responseEntity.consumeContent(); + } 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; + } + return true; } - private JSONObject getJSONFromProvider(String json_url) throws IOException, JSONException { - String json_file_content = getStringFromProvider(json_url); - return new JSONObject(json_file_content); + + /** + * 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) { + String type_of_certificate = task.getString(ConfigHelper.TYPE_OF_CERTIFICATE); + try { + JSONObject provider_json = ConfigHelper.getJsonFromSharedPref(ConfigHelper.PROVIDER_KEY); + URL provider_main_url = new URL(provider_json.getString(ConfigHelper.API_URL_KEY)); + String new_cert_string_url = provider_main_url.toString() + "/" + provider_json.getString(ConfigHelper.API_VERSION_KEY) + "/" + ConfigHelper.CERT_KEY; + + if(type_of_certificate.equalsIgnoreCase(ConfigHelper.AUTHED_CERTIFICATE)) { + HttpCookie session_id_cookie = new HttpCookie(task.getString(ConfigHelper.SESSION_ID_COOKIE_KEY), task.getString(ConfigHelper.SESSION_ID_KEY)); + + CookieManager cookieManager = new CookieManager(); + cookieManager.getCookieStore().add(provider_main_url.toURI(), session_id_cookie); + CookieHandler.setDefault(cookieManager); + } + + boolean danger_on = ConfigHelper.getBoolFromSharedPref(ConfigHelper.DANGER_ON); + String cert_string = getStringFromProvider(new_cert_string_url, danger_on); + if(!cert_string.isEmpty()) { + ConfigHelper.saveSharedPref(ConfigHelper.CERT_KEY, cert_string); + return true; + } 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/src/se/leap/leapclient/ProviderAPIResultReceiver.java b/src/se/leap/leapclient/ProviderAPIResultReceiver.java index a6a8d9d8..e32f6ffd 100644 --- a/src/se/leap/leapclient/ProviderAPIResultReceiver.java +++ b/src/se/leap/leapclient/ProviderAPIResultReceiver.java @@ -4,6 +4,11 @@ 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;
@@ -11,11 +16,16 @@ public class ProviderAPIResultReceiver extends ResultReceiver { 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);
}
diff --git a/src/se/leap/leapclient/ProviderListContent.java b/src/se/leap/leapclient/ProviderListContent.java index dd227bfd..8727b16b 100644 --- a/src/se/leap/leapclient/ProviderListContent.java +++ b/src/se/leap/leapclient/ProviderListContent.java @@ -1,6 +1,5 @@ package se.leap.leapclient;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -11,49 +10,48 @@ import java.util.Map; import org.json.JSONException;
import org.json.JSONObject;
-
+/**
+ * Models the provider list shown in the ConfigurationWizard.
+ *
+ * @author parmegv
+ *
+ */
public class ProviderListContent {
- /**
- * An array of sample (dummy) items.
- */
public static List<ProviderItem> ITEMS = new ArrayList<ProviderItem>();
- /**
- * A map of sample (dummy) items, by ID.
- */
public static Map<String, ProviderItem> ITEM_MAP = new HashMap<String, ProviderItem>();
-
- static {
- //addItem(new ProviderItem("1", "bitmask", "https://bitmask.net/provider.json", "https://api.bitmask.net:4430/1/config/eip-service.json"));
- }
+ /**
+ * 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);
}
/**
- * A dummy item representing a piece of content.
+ * A provider item.
*/
public static class ProviderItem {
public boolean custom = false;
public String id;
public String name;
public String provider_json_url;
+ public JSONObject provider_json;
public String provider_json_filename;
public String eip_service_json_url;
public String cert_json_url;
-
- public ProviderItem(String id, String name, String provider_json_url, String eip_service_json_url, String cert_json_url) {
- this.id = id;
- this.name = name;
- this.provider_json_url = provider_json_url;
- this.eip_service_json_url = eip_service_json_url;
- this.cert_json_url = cert_json_url;
- }
+ public boolean danger_on = false;
- public ProviderItem(String name, InputStream urls_file_input_stream, boolean custom) {
+ /**
+ * @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, boolean custom, boolean danger_on) {
try {
byte[] urls_file_bytes = new byte[urls_file_input_stream.available()];
@@ -62,11 +60,12 @@ public class ProviderListContent { JSONObject file_contents = new JSONObject(urls_file_content);
id = name;
this.name = name;
- provider_json_url = (String) file_contents.get("json_provider");
- provider_json_filename = (String) file_contents.get("assets_json_provider");
- eip_service_json_url = (String) file_contents.get("json_eip_service");
- cert_json_url = (String) file_contents.get("cert");
+ provider_json_url = file_contents.getString(ConfigHelper.PROVIDER_JSON_URL);
+ provider_json_filename = file_contents.getString("assets_json_provider");
+ eip_service_json_url = file_contents.getString("json_eip_service");
+ cert_json_url = file_contents.getString(ConfigHelper.CERT_KEY);
this.custom = custom;
+ this.danger_on = danger_on;
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
@@ -76,26 +75,29 @@ public class ProviderListContent { }
}
- public ProviderItem(String name, FileInputStream provider_json, boolean custom) {
+ /**
+ * @param name of the provider
+ * @param provider_json_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
+ * @param danger_on if the user trusts completely the new provider
+ */
+ public ProviderItem(String name, String provider_json_url, JSONObject provider_json, boolean custom, boolean danger_on) {
try {
- byte[] urls_file_bytes = new byte[provider_json.available()];
- provider_json.read(urls_file_bytes);
- String urls_file_content = new String(urls_file_bytes);
- JSONObject file_contents = new JSONObject(urls_file_content);
id = name;
this.name = name;
- eip_service_json_url = (String) file_contents.get("api_uri") + ConfigHelper.eip_service_api_path;
- cert_json_url = (String) file_contents.get("ca_cert_uri");
+ this.provider_json_url = provider_json_url;
+ this.provider_json = provider_json;
+ eip_service_json_url = provider_json.getString(ConfigHelper.API_URL_KEY) + "/" + provider_json.getString(ConfigHelper.API_VERSION_KEY) + "/" + ConfigHelper.EIP_SERVICE_API_PATH;
+ cert_json_url = (String) provider_json.get("ca_cert_uri");
this.custom = custom;
+ this.danger_on = danger_on;
if(custom)
provider_json_filename = name + "_provider.json".replaceFirst("__", "_");
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
}
}
diff --git a/src/se/leap/leapclient/ProviderListFragment.java b/src/se/leap/leapclient/ProviderListFragment.java index 4316e9f3..ee3ee8ea 100644 --- a/src/se/leap/leapclient/ProviderListFragment.java +++ b/src/se/leap/leapclient/ProviderListFragment.java @@ -2,9 +2,6 @@ package se.leap.leapclient; import se.leap.leapclient.ProviderListContent.ProviderItem;
import android.app.Activity;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.os.Bundle;
import android.view.LayoutInflater;
diff --git a/vim-plugin/basic-adt.vim b/vim-plugin/basic-adt.vim new file mode 100644 index 00000000..366b3f20 --- /dev/null +++ b/vim-plugin/basic-adt.vim @@ -0,0 +1,47 @@ +function! AndroidRun() + let new_project_root = s:Find_in_parent("AndroidManifest.xml", s:windowdir() ,$HOME) + echo "Found Android Project at: " . new_project_root + echo "Windowdir: " . s:windowdir() + if new_project_root != "Nothing" + let b:project_root = new_project_root + new + set buftype=nofile + silent! exec "r!../run.sh SII b:project_root + endif +endfunc + +" Find_in_parent +" " find the file argument and returns the path to it. +" " Starting with the current working dir, it walks up the parent folders +" " until it finds the file, or it hits the stop dir. +" " If it doesn't find it, it returns "Nothing" +function s:Find_in_parent(fln,flsrt,flstp) + let here = a:flsrt + while ( strlen( here) > 0 ) + if filereadable( here . "/" . a:fln ) + return here + endif + let fr = match(here, "/[^/]*$") + if fr == -1 + break + endif + let here = strpart(here, 0, fr) + if here == a:flstp + break + endif + endwhile + return "Nothing" +endfunc + +" windowdir +" " Gets the directory for the file in the current window +" " Or the current working dir if there isn't one for the window. +" " Use tr to allow that other OS paths, too +function s:windowdir() + if winbufnr(0) == -1 + let unislash = getcwd() + else + let unislash = fnamemodify(bufname(winbufnr(0)), ':p:h') + endif + return tr(unislash, '\', '/') +endfunc |