diff options
Diffstat (limited to 'app/src/main')
27 files changed, 1160 insertions, 1028 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d5081b8d..7d1063ef 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,7 +36,7 @@ android:label="@string/app" > <service - android:name="se.leap.bitmaskclient.VoidVpnService" + android:name="se.leap.bitmaskclient.eip.VoidVpnService" android:permission="android.permission.BIND_VPN_SERVICE"> <intent-filter> <action android:name="android.net.VpnService" /> @@ -62,7 +62,9 @@ </receiver> <activity - android:name="se.leap.bitmaskclient.VoidVpnLauncher" /> + android:name="se.leap.bitmaskclient.eip.VoidVpnLauncher" + android:theme="@android:style/Theme.NoDisplay" /> + <activity android:theme="@android:style/Theme.DeviceDefault.Light.Dialog" android:name="de.blinkt.openvpn.activities.DisconnectVPN" /> @@ -99,11 +101,11 @@ android:label="@string/title_about_activity" > </activity> - <service android:name="se.leap.bitmaskclient.EIP" android:exported="false"> + <service android:name="se.leap.bitmaskclient.eip.EIP" android:exported="false"> <intent-filter> - <action android:name="se.leap.bitmaskclient.UPDATE_EIP_SERVICE"/> - <action android:name="se.leap.bitmaskclient.START_EIP"/> - <action android:name="se.leap.bitmaskclient.STOP_EIP"/> + <action android:name="se.leap.bitmaskclient.eip.UPDATE_EIP_SERVICE"/> + <action android:name="se.leap.bitmaskclient.eip.START_EIP"/> + <action android:name="se.leap.bitmaskclient.eip.STOP_EIP"/> </intent-filter> </service> </application> diff --git a/app/src/main/java/de/blinkt/openvpn/core/GetRestrictionReceiver.java b/app/src/main/java/de/blinkt/openvpn/core/GetRestrictionReceiver.java new file mode 100644 index 00000000..5b1dda58 --- /dev/null +++ b/app/src/main/java/de/blinkt/openvpn/core/GetRestrictionReceiver.java @@ -0,0 +1,47 @@ +package de.blinkt.openvpn.core; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.RestrictionEntry; +import android.os.Build; +import android.os.Bundle; + +import java.util.ArrayList; + +import se.leap.bitmaskclient.R; + +/** + * Created by arne on 25.07.13. + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +public class GetRestrictionReceiver extends BroadcastReceiver { + @Override + public void onReceive(final Context context, Intent intent) { + final PendingResult result = goAsync(); + + new Thread() { + @Override + public void run() { + final Bundle extras = new Bundle(); + + ArrayList<RestrictionEntry> restrictionEntries = initRestrictions(context); + + extras.putParcelableArrayList(Intent.EXTRA_RESTRICTIONS_LIST, restrictionEntries); + result.setResult(Activity.RESULT_OK,null,extras); + result.finish(); + } + }.run(); + } + + private ArrayList<RestrictionEntry> initRestrictions(Context context) { + ArrayList<RestrictionEntry> restrictions = new ArrayList<RestrictionEntry>(); + RestrictionEntry allowChanges = new RestrictionEntry("allow_changes",false); + allowChanges.setTitle(context.getString(R.string.allow_vpn_changes)); + restrictions.add(allowChanges); + + return restrictions; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java b/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java index 6d025422..6c4e517b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java @@ -1,15 +1,10 @@ package se.leap.bitmaskclient; import android.app.Activity; -import android.app.Fragment; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import android.widget.TextView; -import se.leap.bitmaskclient.R; public class AboutActivity extends Activity { diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java index c95d0c8b..c0f0b0c3 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java @@ -16,11 +16,15 @@ */ package se.leap.bitmaskclient; +import android.util.Base64; + +import org.json.JSONException; +import org.json.JSONObject; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; -import java.lang.IllegalArgumentException; import java.security.KeyFactory; import java.security.KeyStore; import java.security.KeyStoreException; @@ -33,13 +37,6 @@ import java.security.interfaces.RSAPrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; -import org.json.JSONException; -import org.json.JSONObject; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Base64; - /** * Stores constants, and implements auxiliary methods used across all LEAP Android classes. * diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 364a79af..8143d8d6 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -14,17 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - package se.leap.bitmaskclient; - -import org.json.JSONException; -import org.json.JSONObject; - -import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.ProviderAPIResultReceiver.Receiver; -import se.leap.bitmaskclient.FragmentManagerEnhanced; -import se.leap.bitmaskclient.SignUpDialog; - -import de.blinkt.openvpn.activities.LogWindow; +package se.leap.bitmaskclient; import android.app.Activity; import android.app.AlertDialog; @@ -41,11 +31,16 @@ import android.os.ResultReceiver; import android.util.Log; import android.view.Menu; import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; -import android.widget.Toast; + +import org.json.JSONException; +import org.json.JSONObject; + +import de.blinkt.openvpn.activities.LogWindow; +import se.leap.bitmaskclient.eip.Constants; +import se.leap.bitmaskclient.eip.EIP; +import se.leap.bitmaskclient.eip.EipStatus; /** * The main user facing Activity of LEAP Android, consisting of status, controls, @@ -54,11 +49,12 @@ import android.widget.Toast; * @author Sean Leonard <meanderingcode@aetherislands.net> * @author parmegv */ -public class Dashboard extends Activity implements LogInDialog.LogInDialogInterface, SignUpDialog.SignUpDialogInterface, Receiver { +public class Dashboard extends Activity implements LogInDialog.LogInDialogInterface, SignUpDialog.SignUpDialogInterface, ProviderAPIResultReceiver.Receiver { protected static final int CONFIGURE_LEAP = 0; protected static final int SWITCH_PROVIDER = 1; + final public static String TAG = Dashboard.class.getSimpleName(); final public static String SHARED_PREFERENCES = "LEAPPreferences"; final public static String ACTION_QUIT = "quit"; public static final String REQUEST_CODE = "request_code"; @@ -66,21 +62,17 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf public static final String START_ON_BOOT = "dashboard start on boot"; final public static String ON_BOOT = "dashboard on boot"; public static final String APP_VERSION = "bitmask version"; - final public static String TAG = Dashboard.class.getSimpleName(); - - private EipServiceFragment eipFragment; - private ProgressBar mProgressBar; - private TextView eipStatus; - private static Context app; - protected static SharedPreferences preferences; - private static Provider provider; - - private boolean authed_eip = false; + private static Context app; + protected static SharedPreferences preferences; + private FragmentManagerEnhanced fragment_manager; + private ProgressBar mProgressBar; + private TextView status_message; public ProviderAPIResultReceiver providerAPI_result_receiver; - private FragmentManagerEnhanced fragment_manager; + private static boolean authed_eip; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -95,32 +87,33 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf fragment_manager = new FragmentManagerEnhanced(getFragmentManager()); handleVersion(); - authed_eip = preferences.getBoolean(EIP.AUTHED_EIP, false); - if (preferences.getString(Provider.KEY, "").isEmpty()) - startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); - else - buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false)); + boolean provider_configured = preferences.getString(Constants.KEY, "").isEmpty(); + if (provider_configured) + startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); + else + buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false)); } private void handleVersion() { try { int versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; int lastDetectedVersion = preferences.getInt(APP_VERSION, 0); - preferences.edit().putInt(APP_VERSION, versionCode); + preferences.edit().putInt(APP_VERSION, versionCode).apply(); Log.d("Dashboard", "detected version code: " + versionCode); Log.d("Dashboard", "last detected version code: " + lastDetectedVersion); switch(versionCode) { case 91: // 0.6.0 without Bug #5999 case 101: // 0.8.0 - if(!preferences.getString(EIP.KEY, "").isEmpty()) { + if(!preferences.getString(Constants.KEY, "").isEmpty()) { Intent rebuildVpnProfiles = new Intent(getApplicationContext(), EIP.class); - rebuildVpnProfiles.setAction(EIP.ACTION_REBUILD_PROFILES); + rebuildVpnProfiles.setAction(Constants.ACTION_UPDATE_EIP_SERVICE); startService(rebuildVpnProfiles); } break; } } catch (NameNotFoundException e) { + Log.d(TAG, "Handle version didn't find any " + getPackageName() + " package"); } } @@ -134,30 +127,28 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf super.onPause(); } - @Override - protected void onActivityResult(int requestCode, int resultCode, final Intent data){ - if ( requestCode == CONFIGURE_LEAP || requestCode == SWITCH_PROVIDER) { - // It should be equivalent: if ( (requestCode == CONFIGURE_LEAP) || (data!= null && data.hasExtra(STOP_FIRST))) { - if ( resultCode == RESULT_OK ){ - preferences.edit().putInt(EIP.PARSED_SERIAL, 0).commit(); - preferences.edit().putBoolean(EIP.AUTHED_EIP, authed_eip).commit(); - - Intent updateEIP = new Intent(getApplicationContext(), EIP.class); - updateEIP.setAction(EIP.ACTION_UPDATE_EIP_SERVICE); - startService(updateEIP); - - buildDashboard(false); - invalidateOptionsMenu(); - if(data != null && data.hasExtra(LogInDialog.TAG)) { - View view = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0); - logInDialog(Bundle.EMPTY); - } - } else if(resultCode == RESULT_CANCELED && (data == null || data.hasExtra(ACTION_QUIT))) { - finish(); - } else - configErrorDialog(); + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data){ + Log.d(TAG, "onActivityResult: requestCode = " + requestCode); + if ( requestCode == CONFIGURE_LEAP || requestCode == SWITCH_PROVIDER) { + // It should be equivalent: if ( (requestCode == CONFIGURE_LEAP) || (data!= null && data.hasExtra(STOP_FIRST))) { + if ( resultCode == RESULT_OK ){ + preferences.edit().putInt(Constants.PARSED_SERIAL, 0).apply(); + preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); + updateEipService(); + buildDashboard(false); + invalidateOptionsMenu(); + if(data != null && data.hasExtra(LogInDialog.TAG)) { + logInDialog(Bundle.EMPTY); } + } else if(resultCode == RESULT_CANCELED && (data == null || data.hasExtra(ACTION_QUIT))) { + finish(); + } else + configErrorDialog(); + } else if(requestCode == EIP.DISCONNECT) { + EipStatus.getInstance().setConnectedOrDisconnected(); } + } /** * Dialog shown when encountering a configuration error. Such errors require @@ -178,7 +169,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf .setNegativeButton(getResources().getString(R.string.setup_error_close_button), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - preferences.edit().remove(Provider.KEY).commit(); + preferences.edit().remove(Provider.KEY).apply(); finish(); } }) @@ -190,7 +181,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf * service dependent UI elements to include. */ private void buildDashboard(boolean hide_and_turn_on_eip) { - provider = Provider.getInstance(); + Provider provider = Provider.getInstance(); provider.init( this ); setContentView(R.layout.client_dashboard); @@ -199,21 +190,27 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf providerNameTV.setText(provider.getDomain()); providerNameTV.setTextSize(28); - mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); + mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); if ( provider.hasEIP()){ - eipFragment = new EipServiceFragment(); - if (hide_and_turn_on_eip) { - preferences.edit().remove(Dashboard.START_ON_BOOT).commit(); - Bundle arguments = new Bundle(); - arguments.putBoolean(EipServiceFragment.START_ON_BOOT, true); - eipFragment.setArguments(arguments); - } - fragment_manager.replace(R.id.servicesCollection, eipFragment, EipServiceFragment.TAG); - if (hide_and_turn_on_eip) { - onBackPressed(); - } + EipServiceFragment eip_fragment = (EipServiceFragment) fragment_manager.findFragmentByTag(EipServiceFragment.TAG); + if(eip_fragment == null) + eip_fragment = new EipServiceFragment(); + + if (hide_and_turn_on_eip) { + preferences.edit().remove(Dashboard.START_ON_BOOT).apply(); + Bundle arguments = new Bundle(); + arguments.putBoolean(EipServiceFragment.START_ON_BOOT, true); + eip_fragment.setArguments(arguments); + } + + fragment_manager.removePreviousFragment(EipServiceFragment.TAG); + fragment_manager.replace(R.id.servicesCollection, eip_fragment, EipServiceFragment.TAG); + + if (hide_and_turn_on_eip) { + onBackPressed(); + } } } @@ -222,12 +219,12 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf JSONObject provider_json; try { String provider_json_string = preferences.getString(Provider.KEY, ""); - if(provider_json_string.isEmpty() == false) { + if(!provider_json_string.isEmpty()) { provider_json = new JSONObject(provider_json_string); JSONObject service_description = provider_json.getJSONObject(Provider.SERVICE); boolean authed_eip = !LeapSRPSession.getToken().isEmpty(); boolean allow_registered_eip = service_description.getBoolean(Provider.ALLOW_REGISTRATION); - preferences.edit().putBoolean(EIP.ALLOWED_REGISTERED, allow_registered_eip); + preferences.edit().putBoolean(Constants.ALLOWED_REGISTERED, allow_registered_eip).apply(); if(allow_registered_eip) { if(authed_eip) { @@ -268,12 +265,12 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf return true; case R.id.switch_provider: if (Provider.getInstance().hasEIP()){ - if (preferences.getBoolean(EIP.AUTHED_EIP, false)){ + if (preferences.getBoolean(Constants.AUTHED_EIP, false)){ logOut(); } eipStop(); } - preferences.edit().clear().commit(); + preferences.edit().clear().apply(); startActivityForResult(new Intent(this,ConfigurationWizard.class), SWITCH_PROVIDER); return true; case R.id.login_button: @@ -293,7 +290,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf private Intent prepareProviderAPICommand() { mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); - eipStatus = (TextView) findViewById(R.id.eipStatus); + status_message = (TextView) findViewById(R.id.status_message); providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); providerAPI_result_receiver.setReceiver(this); @@ -327,20 +324,15 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf parameters.putString(SessionDialogInterface.PASSWORD, password); mProgressBar.setVisibility(ProgressBar.VISIBLE); - eipStatus.setText(R.string.authenticating_message); + status_message.setText(R.string.authenticating_message); provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); provider_API_command.setAction(ProviderAPI.SRP_AUTH); startService(provider_API_command); } - public void cancelAuthedEipOn() { - EipServiceFragment eipFragment = (EipServiceFragment) getFragmentManager().findFragmentByTag(EipServiceFragment.TAG); - eipFragment.checkEipSwitch(false); - } - public void cancelLoginOrSignup() { - hideProgressBar(); + EipStatus.getInstance().setConnectedOrDisconnected(); } /** @@ -351,8 +343,8 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf if(mProgressBar == null) mProgressBar = (ProgressBar) findViewById(R.id.eipProgress); mProgressBar.setVisibility(ProgressBar.VISIBLE); - if(eipStatus == null) eipStatus = (TextView) findViewById(R.id.eipStatus); - eipStatus.setText(R.string.logout_message); + if(status_message == null) status_message = (TextView) findViewById(R.id.status_message); + status_message.setText(R.string.logout_message); provider_API_command.setAction(ProviderAPI.LOG_OUT); startService(provider_API_command); @@ -382,7 +374,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf parameters.putString(SessionDialogInterface.PASSWORD, password); mProgressBar.setVisibility(ProgressBar.VISIBLE); - eipStatus.setText(R.string.signingup_message); + status_message.setText(R.string.signingup_message); provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); provider_API_command.setAction(ProviderAPI.SRP_REGISTER); @@ -410,6 +402,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf @Override public void onReceiveResult(int resultCode, Bundle resultData) { + Log.d(TAG, "onReceiveResult"); if(resultCode == ProviderAPI.SRP_REGISTRATION_SUCCESSFUL) { String username = resultData.getString(SessionDialogInterface.USERNAME); String password = resultData.getString(SessionDialogInterface.PASSWORD); @@ -426,7 +419,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf invalidateOptionsMenu(); authed_eip = true; - preferences.edit().putBoolean(EIP.AUTHED_EIP, authed_eip).commit(); + preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); downloadAuthedUserCertificate(); } else if(resultCode == ProviderAPI.SRP_AUTHENTICATION_FAILED) { @@ -441,7 +434,7 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf invalidateOptionsMenu(); authed_eip = false; - preferences.edit().putBoolean(EIP.AUTHED_EIP, authed_eip).commit(); + preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); } else if(resultCode == ProviderAPI.LOGOUT_FAILED) { changeStatusMessage(resultCode); @@ -453,22 +446,8 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf hideProgressBar(); setResult(RESULT_OK); - Intent updateEIP = new Intent(getApplicationContext(), EIP.class); - ResultReceiver eip_receiver = new ResultReceiver(new Handler()){ - protected void onReceiveResult(int resultCode, Bundle resultData){ - super.onReceiveResult(resultCode, resultData); - String request = resultData.getString(EIP.REQUEST_TAG); - if (resultCode == Activity.RESULT_OK){ - if(authed_eip) - eipStart(); - else - eipStatus.setText("Certificate updated"); - } - } - }; - updateEIP.putExtra(EIP.RECEIVER_TAG, eip_receiver); - updateEIP.setAction(EIP.ACTION_UPDATE_EIP_SERVICE); - startService(updateEIP); + + updateEipService(); } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { changeStatusMessage(resultCode); hideProgressBar(); @@ -476,23 +455,41 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf } } + private void updateEipService() { + Intent updateEIP = new Intent(getApplicationContext(), EIP.class); + updateEIP.setAction(Constants.ACTION_UPDATE_EIP_SERVICE); + ResultReceiver receiver = new ResultReceiver(new Handler()) { + protected void onReceiveResult(int resultCode, Bundle resultData) { + String request = resultData.getString(Constants.REQUEST_TAG); + if(request.equalsIgnoreCase(Constants.ACTION_UPDATE_EIP_SERVICE)) { + if(resultCode == Activity.RESULT_OK) { + if(authed_eip) + eipStart(); + } + } + } + }; + updateEIP.putExtra(Constants.RECEIVER_TAG, receiver); + startService(updateEIP); + } + private void changeStatusMessage(final int previous_result_code) { // TODO Auto-generated method stub ResultReceiver eip_status_receiver = new ResultReceiver(new Handler()){ protected void onReceiveResult(int resultCode, Bundle resultData){ super.onReceiveResult(resultCode, resultData); - String request = resultData.getString(EIP.REQUEST_TAG); - if(eipStatus == null) eipStatus = (TextView) findViewById(R.id.eipStatus); - if (request.equalsIgnoreCase(EIP.ACTION_IS_EIP_RUNNING)){ + String request = resultData.getString(Constants.REQUEST_TAG); + if(status_message == null) status_message = (TextView) findViewById(R.id.status_message); + if (request.equalsIgnoreCase(Constants.ACTION_IS_EIP_RUNNING)){ if (resultCode == Activity.RESULT_OK){ switch(previous_result_code){ - case ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL: eipStatus.setText(R.string.succesful_authentication_message); break; - case ProviderAPI.SRP_AUTHENTICATION_FAILED: eipStatus.setText(R.string.authentication_failed_message); break; - case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.authed_secured_status); break; - case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.incorrectly_downloaded_certificate_message); break; - case ProviderAPI.LOGOUT_SUCCESSFUL: eipStatus.setText(R.string.logged_out_message); break; - case ProviderAPI.LOGOUT_FAILED: eipStatus.setText(R.string.log_out_failed_message); break; + case ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL: status_message.setText(R.string.succesful_authentication_message); break; + case ProviderAPI.SRP_AUTHENTICATION_FAILED: status_message.setText(R.string.authentication_failed_message); break; + case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: status_message.setText(R.string.authed_secured_status); break; + case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: status_message.setText(R.string.incorrectly_downloaded_certificate_message); break; + case ProviderAPI.LOGOUT_SUCCESSFUL: status_message.setText(R.string.logged_out_message); break; + case ProviderAPI.LOGOUT_FAILED: status_message.setText(R.string.log_out_failed_message); break; } } @@ -500,13 +497,13 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf switch(previous_result_code){ - case ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL: eipStatus.setText(R.string.succesful_authentication_message); break; - case ProviderAPI.SRP_AUTHENTICATION_FAILED: eipStatus.setText(R.string.authentication_failed_message); break; - case ProviderAPI.SRP_REGISTRATION_FAILED: eipStatus.setText(R.string.registration_failed_message); break; + case ProviderAPI.SRP_AUTHENTICATION_SUCCESSFUL: status_message.setText(R.string.succesful_authentication_message); break; + case ProviderAPI.SRP_AUTHENTICATION_FAILED: status_message.setText(R.string.authentication_failed_message); break; + case ProviderAPI.SRP_REGISTRATION_FAILED: status_message.setText(R.string.registration_failed_message); break; case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: break; - case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eipStatus.setText(R.string.incorrectly_downloaded_certificate_message); break; - case ProviderAPI.LOGOUT_SUCCESSFUL: eipStatus.setText(R.string.logged_out_message); break; - case ProviderAPI.LOGOUT_FAILED: eipStatus.setText(R.string.log_out_failed_message); break; + case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: status_message.setText(R.string.incorrectly_downloaded_certificate_message); break; + case ProviderAPI.LOGOUT_SUCCESSFUL: status_message.setText(R.string.logged_out_message); break; + case ProviderAPI.LOGOUT_FAILED: status_message.setText(R.string.log_out_failed_message); break; } } } @@ -544,8 +541,8 @@ public class Dashboard extends Activity implements LogInDialog.LogInDialogInterf private void eipIsRunning(ResultReceiver eip_receiver){ // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? Intent eip_intent = new Intent(this, EIP.class); - eip_intent.setAction(EIP.ACTION_IS_EIP_RUNNING); - eip_intent.putExtra(EIP.RECEIVER_TAG, eip_receiver); + eip_intent.setAction(Constants.ACTION_IS_EIP_RUNNING); + eip_intent.putExtra(Constants.RECEIVER_TAG, eip_receiver); startService(eip_intent); } diff --git a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java b/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java index f78002b0..a44253c6 100644 --- a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java @@ -16,9 +16,6 @@ */ package se.leap.bitmaskclient; -import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.NewProviderDialog.NewProviderDialogInterface; -import se.leap.bitmaskclient.ProviderListContent.ProviderItem; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; diff --git a/app/src/main/java/se/leap/bitmaskclient/EIP.java b/app/src/main/java/se/leap/bitmaskclient/EIP.java deleted file mode 100644 index 2f06def3..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/EIP.java +++ /dev/null @@ -1,524 +0,0 @@ -/** - * Copyright (c) 2013 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -package se.leap.bitmaskclient; - -import android.app.Activity; -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.ResultReceiver; -import android.util.Log; -import de.blinkt.openvpn.LaunchVPN; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.activities.DisconnectVPN; -import de.blinkt.openvpn.core.ConfigParser; -import de.blinkt.openvpn.core.ConfigParser.ConfigParseError; -import de.blinkt.openvpn.core.ProfileManager; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; -import java.io.IOException; -import java.io.StringReader; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Calendar; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Locale; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.TreeMap; -import java.util.Vector; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import se.leap.bitmaskclient.Dashboard; -import se.leap.bitmaskclient.Provider; -import se.leap.bitmaskclient.R; - -/** - * EIP is the abstract base class for interacting with and managing the Encrypted - * Internet Proxy connection. Connections are started, stopped, and queried through - * this IntentService. - * Contains logic for parsing eip-service.json from the provider, configuring and selecting - * gateways, and controlling {@link de.blinkt.openvpn.core.OpenVPNService} connections. - * - * @author Sean Leonard <meanderingcode@aetherislands.net> - * @author Parménides GV <parmegv@sdf.org> - */ -public final class EIP extends IntentService { - - public final static String AUTHED_EIP = "authed eip"; - public final static String ACTION_CHECK_CERT_VALIDITY = "se.leap.bitmaskclient.CHECK_CERT_VALIDITY"; - public final static String ACTION_START_EIP = "se.leap.bitmaskclient.START_EIP"; - public final static String ACTION_STOP_EIP = "se.leap.bitmaskclient.STOP_EIP"; - public final static String ACTION_UPDATE_EIP_SERVICE = "se.leap.bitmaskclient.UPDATE_EIP_SERVICE"; - public final static String ACTION_IS_EIP_RUNNING = "se.leap.bitmaskclient.IS_RUNNING"; - public final static String ACTION_REBUILD_PROFILES = "se.leap.bitmaskclient.REBUILD_PROFILES"; - public final static String EIP_NOTIFICATION = "EIP_NOTIFICATION"; - public final static String STATUS = "eip status"; - public final static String DATE_FROM_CERTIFICATE = "date from certificate"; - public final static String ALLOWED_ANON = "allow_anonymous"; - public final static String ALLOWED_REGISTERED = "allow_registration"; - public final static String CERTIFICATE = "cert"; - public final static String PRIVATE_KEY = "private_key"; - public final static String KEY = "eip"; - public final static String PARSED_SERIAL = "eip_parsed_serial"; - public final static String SERVICE_API_PATH = "config/eip-service.json"; - public final static String RECEIVER_TAG = "receiverTag"; - public final static String REQUEST_TAG = "requestTag"; - public final static String TAG = EIP.class.getSimpleName(); - private static SharedPreferences preferences; - - private static Context context; - private static ResultReceiver mReceiver; - private static boolean mBound = false; - - private static JSONObject eipDefinition = null; - - private static OVPNGateway activeGateway = null; - - protected static ConnectionStatus lastConnectionStatusLevel; - protected static boolean mIsDisconnecting = false; - protected static boolean mIsStarting = false; - - public static SimpleDateFormat certificate_date_format = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US); - public EIP(){ - super("LEAPEIP"); - } - - @Override - public void onCreate() { - super.onCreate(); - - context = getApplicationContext(); - - preferences = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE); - } - - @Override - public void onDestroy() { - - mBound = false; - - super.onDestroy(); - } - - - @Override - protected void onHandleIntent(Intent intent) { - String action = intent.getAction(); - mReceiver = intent.getParcelableExtra(RECEIVER_TAG); - - if ( action == ACTION_START_EIP ) - startEIP(); - else if ( action == ACTION_STOP_EIP ) - stopEIP(); - else if ( action == ACTION_IS_EIP_RUNNING ) - isRunning(); - else if ( action == ACTION_UPDATE_EIP_SERVICE ) - updateEIPService(); - else if ( action == ACTION_CHECK_CERT_VALIDITY ) - checkCertValidity(); - else if ( action == ACTION_REBUILD_PROFILES ) - updateGateways(); - } - - /** - * Initiates an EIP connection by selecting a gateway and preparing and sending an - * Intent to {@link se.leap.openvpn.LaunchVPN}. - * It also sets up early routes. - */ - private void startEIP() { - earlyRoutes(); - activeGateway = selectGateway(); - - if(activeGateway != null && activeGateway.mVpnProfile != null) { - mReceiver = EipServiceFragment.getReceiver(); - launchActiveGateway(); - } - } - - /** - * Early routes are routes that block traffic until a new - * VpnService is started properly. - */ - private void earlyRoutes() { - Intent void_vpn_launcher = new Intent(context, VoidVpnLauncher.class); - void_vpn_launcher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(void_vpn_launcher); - } - - /** - * Choose a gateway to connect to based on timezone from system locale data - * - * @return The gateway to connect to - */ - private OVPNGateway selectGateway() { - String closest_location = closestGateway(); - String chosen_host = chooseHost(closest_location); - - return new OVPNGateway(chosen_host); - } - - private String closestGateway() { - TreeMap<Integer, Set<String>> offsets = calculateOffsets(); - return offsets.isEmpty() ? "" : offsets.firstEntry().getValue().iterator().next(); - } - - private TreeMap<Integer, Set<String>> calculateOffsets() { - TreeMap<Integer, Set<String>> offsets = new TreeMap<Integer, Set<String>>(); - - int localOffset = Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 3600000; - - JSONObject locations = availableLocations(); - Iterator<String> locations_names = locations.keys(); - while(locations_names.hasNext()) { - try { - String location_name = locations_names.next(); - JSONObject location = locations.getJSONObject(location_name); - - int dist = timezoneDistance(localOffset, location.optInt("timezone")); - - Set<String> set = (offsets.get(dist) != null) ? - offsets.get(dist) : new HashSet<String>(); - - set.add(location_name); - offsets.put(dist, set); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - return offsets; - } - - private JSONObject availableLocations() { - JSONObject locations = null; - try { - if(eipDefinition == null) updateEIPService(); - locations = eipDefinition.getJSONObject("locations"); - } catch (JSONException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - - return locations; - } - - private int timezoneDistance(int local_timezone, int remote_timezone) { - // Distance along the numberline of Prime Meridian centric, assumes UTC-11 through UTC+12 - int dist = Math.abs(local_timezone - remote_timezone); - - // Farther than 12 timezones and it's shorter around the "back" - if (dist > 12) - dist = 12 - (dist -12); // Well i'll be. Absolute values make equations do funny things. - - return dist; - } - - private String chooseHost(String location) { - String chosen_host = ""; - try { - JSONArray gateways = eipDefinition.getJSONArray("gateways"); - for (int i = 0; i < gateways.length(); i++) { - JSONObject gw = gateways.getJSONObject(i); - if ( gw.getString("location").equalsIgnoreCase(location) || location.isEmpty()){ - chosen_host = eipDefinition.getJSONObject("locations").getJSONObject(gw.getString("location")).getString("name"); - break; - } - } - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return chosen_host; - } - - private void launchActiveGateway() { - Intent intent = new Intent(this,LaunchVPN.class); - intent.setAction(Intent.ACTION_MAIN); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(LaunchVPN.EXTRA_KEY, activeGateway.mVpnProfile.getUUID().toString() ); - intent.putExtra(LaunchVPN.EXTRA_NAME, activeGateway.mVpnProfile.getName() ); - intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); - intent.putExtra(RECEIVER_TAG, mReceiver); - startActivity(intent); - } - - /** - * Disconnects the EIP connection gracefully through the bound service or forcefully - * if there is no bound service. Sends a message to the requesting ResultReceiver. - */ - private void stopEIP() { - if(isConnected()) { - Intent disconnect_vpn = new Intent(this, DisconnectVPN.class); - disconnect_vpn.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(disconnect_vpn); - mIsDisconnecting = true; - lastConnectionStatusLevel = ConnectionStatus.UNKNOWN_LEVEL; // Wait for the decision of the user - Log.d(TAG, "mIsDisconnecting = true"); - } - - tellToReceiver(ACTION_STOP_EIP, Activity.RESULT_OK); - } - - private void tellToReceiver(String action, int resultCode) { - if (mReceiver != null){ - Bundle resultData = new Bundle(); - resultData.putString(REQUEST_TAG, action); - mReceiver.send(resultCode, resultData); - } - } - - /** - * Checks the last stored status notified by ics-openvpn - * Sends <code>Activity.RESULT_CANCELED</code> to the ResultReceiver that made the - * request if it's not connected, <code>Activity.RESULT_OK</code> otherwise. - */ - - private void isRunning() { - int resultCode = Activity.RESULT_CANCELED; - boolean is_connected = isConnected(); - - resultCode = (is_connected) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; - - tellToReceiver(ACTION_IS_EIP_RUNNING, resultCode); - } - - protected static boolean isConnected() { - return lastConnectionStatusLevel != null && lastConnectionStatusLevel.equals(ConnectionStatus.LEVEL_CONNECTED) && !mIsDisconnecting; - } - - /** - * Loads eip-service.json from SharedPreferences and calls {@link updateGateways()} - * to parse gateway definitions. - * TODO Implement API call to refresh eip-service.json from the provider - */ - private void updateEIPService() { - try { - String eip_definition_string = preferences.getString(KEY, ""); - if(eip_definition_string.isEmpty() == false) { - eipDefinition = new JSONObject(eip_definition_string); - } - deleteAllVpnProfiles(); - updateGateways(); - if(mReceiver != null) mReceiver.send(Activity.RESULT_OK, Bundle.EMPTY); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - private void deleteAllVpnProfiles() { - ProfileManager vpl = ProfileManager.getInstance(context); - Collection<VpnProfile> profiles = vpl.getProfiles(); - profiles.removeAll(profiles); - } - - /** - * Walk the list of gateways defined in eip-service.json and parse them into - * OVPNGateway objects. - * TODO Store the OVPNGateways (as Serializable) in SharedPreferences - */ - private void updateGateways(){ - JSONArray gatewaysDefined = null; - try { - if(eipDefinition == null) updateEIPService(); - gatewaysDefined = eipDefinition.getJSONArray("gateways"); - for ( int i=0 ; i < gatewaysDefined.length(); i++ ){ - JSONObject gw = null; - gw = gatewaysDefined.getJSONObject(i); - - if ( gw.getJSONObject("capabilities").getJSONArray("transport").toString().contains("openvpn") ) - new OVPNGateway(gw); - } - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - preferences.edit().putInt(PARSED_SERIAL, eipDefinition.optInt(Provider.API_RETURN_SERIAL)).commit(); - } - - private void checkCertValidity() { - String certificate = preferences.getString(CERTIFICATE, ""); - checkCertValidity(certificate); - } - - private void checkCertValidity(String certificate_string) { - if(!certificate_string.isEmpty()) { - X509Certificate certificate = ConfigHelper.parseX509CertificateFromString(certificate_string); - - Calendar offset_date = calculateOffsetCertificateValidity(certificate); - Bundle result = new Bundle(); - result.putString(REQUEST_TAG, ACTION_CHECK_CERT_VALIDITY); - try { - Log.d(TAG, "offset_date = " + offset_date.getTime().toString()); - certificate.checkValidity(offset_date.getTime()); - mReceiver.send(Activity.RESULT_OK, result); - Log.d(TAG, "Valid certificate"); - } catch(CertificateExpiredException e) { - mReceiver.send(Activity.RESULT_CANCELED, result); - Log.d(TAG, "Updating certificate"); - } catch(CertificateNotYetValidException e) { - mReceiver.send(Activity.RESULT_CANCELED, result); - } - } - } - - private Calendar calculateOffsetCertificateValidity(X509Certificate certificate) { - String current_date = certificate_date_format.format(Calendar.getInstance().getTime()).toString(); - - String date_string = preferences.getString(DATE_FROM_CERTIFICATE, current_date); - - Calendar offset_date = Calendar.getInstance(); - try { - Date date = certificate_date_format.parse(date_string); - long difference = Math.abs(date.getTime() - certificate.getNotAfter().getTime())/2; - long current_date_millis = offset_date.getTimeInMillis(); - offset_date.setTimeInMillis(current_date_millis + difference); - Log.d(TAG, "certificate not after = " + certificate.getNotAfter()); - } catch(ParseException e) { - e.printStackTrace(); - } - - return offset_date; - } - - /** - * OVPNGateway provides objects defining gateways and their options and metadata. - * Each instance contains a VpnProfile for OpenVPN specific data and member - * variables describing capabilities and location - * - * @author Sean Leonard <meanderingcode@aetherislands.net> - */ - private class OVPNGateway { - - private String TAG = "OVPNGateway"; - - private String mName; - private VpnProfile mVpnProfile; - private JSONObject mGateway; - private HashMap<String,Vector<Vector<String>>> options = new HashMap<String, Vector<Vector<String>>>(); - - - /** - * Attempts to retrieve a VpnProfile by name and build an OVPNGateway around it. - * FIXME This needs to become a findGatewayByName() method - * - * @param name The hostname of the gateway to inflate - */ - private OVPNGateway(String name){ - mName = name; - - this.loadVpnProfile(); - } - - private void loadVpnProfile() { - ProfileManager vpl = ProfileManager.getInstance(context); - try { - if ( mName == null ) - mVpnProfile = vpl.getProfiles().iterator().next(); - else - mVpnProfile = vpl.getProfileByName(mName); - } catch (NoSuchElementException e) { - updateEIPService(); - this.loadVpnProfile(); // FIXME catch infinite loops - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - /** - * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json - * and create a VpnProfile belonging to it. - * - * @param gateway The JSON OpenVPN gateway definition to parse - */ - protected OVPNGateway(JSONObject gateway){ - - mGateway = gateway; - - // Currently deletes VpnProfile for host, if there already is one, and builds new - ProfileManager vpl = ProfileManager.getInstance(context); - Collection<VpnProfile> profiles = vpl.getProfiles(); - for (Iterator<VpnProfile> it = profiles.iterator(); it.hasNext(); ){ - VpnProfile p = it.next(); - - if ( p.mName.equalsIgnoreCase( mName ) ) { - it.remove(); - vpl.removeProfile(context, p); - } - } - - this.createVPNProfile(); - - vpl.addProfile(mVpnProfile); - vpl.saveProfile(context, mVpnProfile); - vpl.saveProfileList(context); - } - - /** - * Create and attach the VpnProfile to our gateway object - */ - protected void createVPNProfile(){ - try { - ConfigParser cp = new ConfigParser(); - - JSONObject openvpn_configuration = eipDefinition.getJSONObject("openvpn_configuration"); - VpnConfigGenerator vpn_configuration_generator = new VpnConfigGenerator(preferences, openvpn_configuration, mGateway); - String configuration = vpn_configuration_generator.generate(); - - cp.parseConfig(new StringReader(configuration)); - mVpnProfile = cp.convertProfile(); - mVpnProfile.mName = mName = locationAsName(); - Log.v(TAG,"Created VPNProfile"); - - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (ConfigParseError e) { - // FIXME We didn't get a VpnProfile! Error handling! and log level - Log.v(TAG,"Error creating VPNProfile"); - e.printStackTrace(); - } catch (IOException e) { - // FIXME We didn't get a VpnProfile! Error handling! and log level - Log.v(TAG,"Error creating VPNProfile"); - e.printStackTrace(); - } - } - - - public String locationAsName() { - try { - return eipDefinition.getJSONObject("locations").getJSONObject(mGateway.getString("location")).getString("name"); - } catch (JSONException e) { - Log.v(TAG,"Couldn't read gateway name for profile creation! Returning original name = " + mName); - e.printStackTrace(); - return (mName != null) ? mName : ""; - } - } - } -} diff --git a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java index 6d223dd6..592a9552 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipServiceFragment.java @@ -1,14 +1,5 @@ package se.leap.bitmaskclient; -import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.ProviderAPIResultReceiver; -import se.leap.bitmaskclient.ProviderAPIResultReceiver.Receiver; -import se.leap.bitmaskclient.Dashboard; - -import de.blinkt.openvpn.activities.LogWindow; -import de.blinkt.openvpn.core.VpnStatus; -import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; -import de.blinkt.openvpn.core.VpnStatus.StateListener; import android.app.Activity; import android.app.AlertDialog; import android.app.Fragment; @@ -19,99 +10,110 @@ import android.os.Handler; import android.os.ResultReceiver; import android.util.Log; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CompoundButton; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; import android.widget.Switch; import android.widget.TextView; -public class EipServiceFragment extends Fragment implements StateListener, OnCheckedChangeListener { +import java.util.Observable; +import java.util.Observer; + +import de.blinkt.openvpn.activities.DisconnectVPN; +import se.leap.bitmaskclient.eip.Constants; +import se.leap.bitmaskclient.eip.EIP; +import se.leap.bitmaskclient.eip.EipStatus; + +public class EipServiceFragment extends Fragment implements Observer, CompoundButton.OnCheckedChangeListener { - protected static final String IS_EIP_PENDING = "is_eip_pending"; + public static String TAG = "se.leap.bitmask.EipServiceFragment"; + + protected static final String IS_PENDING = TAG + ".is_pending"; + protected static final String IS_CONNECTED = TAG + ".is_connected"; + protected static final String STATUS_MESSAGE = TAG + ".status_message"; public static final String START_ON_BOOT = "start on boot"; - - private View eipFragment; - private static Switch eipSwitch; - private View eipDetail; - private TextView eipStatus; + private View eipFragment; + private static Switch eipSwitch; + private TextView status_message; + + private static Activity parent_activity; private static EIPReceiver mEIPReceiver; + private static EipStatus eip_status; + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + parent_activity = activity; + } - public static String TAG = "se.leap.bitmask.EipServiceFragment"; + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + eip_status = EipStatus.getInstance(); + eip_status.addObserver(this); + mEIPReceiver = new EIPReceiver(new Handler()); + } - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - eipFragment = inflater.inflate(R.layout.eip_service_fragment, container, false); - eipDetail = ((RelativeLayout) eipFragment.findViewById(R.id.eipDetail)); - eipDetail.setVisibility(View.VISIBLE); + eipFragment = inflater.inflate(R.layout.eip_service_fragment, container, false); + View eipDetail = eipFragment.findViewById(R.id.eipDetail); + eipDetail.setVisibility(View.VISIBLE); - View eipSettings = eipFragment.findViewById(R.id.eipSettings); - eipSettings.setVisibility(View.GONE); // FIXME too! + View eipSettings = eipFragment.findViewById(R.id.eipSettings); + eipSettings.setVisibility(View.GONE); // FIXME too! - if (EIP.mIsStarting) - eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); - - eipStatus = (TextView) eipFragment.findViewById(R.id.eipStatus); - - eipSwitch = (Switch) eipFragment.findViewById(R.id.eipSwitch); - eipSwitch.setOnCheckedChangeListener(this); - - if(getArguments() != null && getArguments().containsKey(START_ON_BOOT) && getArguments().getBoolean(START_ON_BOOT)) - startEipFromScratch(); + if (eip_status.isConnecting()) + eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); - return eipFragment; - } + status_message = (TextView) eipFragment.findViewById(R.id.status_message); - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + eipSwitch = (Switch) eipFragment.findViewById(R.id.eipSwitch); + Log.d(TAG, "onCreateView, eipSwitch is checked? " + eipSwitch.isChecked()); + eipSwitch.setOnCheckedChangeListener(this); - mEIPReceiver = new EIPReceiver(new Handler()); - - if (savedInstanceState != null) - EIP.mIsStarting = savedInstanceState.getBoolean(IS_EIP_PENDING); - } - - @Override - public void onResume() { - super.onResume(); - - VpnStatus.addStateListener(this); + if(getArguments() != null && getArguments().containsKey(START_ON_BOOT) && getArguments().getBoolean(START_ON_BOOT)) + startEipFromScratch(); - eipCommand(EIP.ACTION_CHECK_CERT_VALIDITY); - } - - @Override - public void onPause() { - super.onPause(); + if (savedInstanceState != null) { + setStatusMessage(savedInstanceState.getString(STATUS_MESSAGE)); + if(savedInstanceState.getBoolean(IS_PENDING)) + eip_status.setConnecting(); + else if(savedInstanceState.getBoolean(IS_CONNECTED)) { + eip_status.setConnectedOrDisconnected(); + } + } + return eipFragment; + } - VpnStatus.removeStateListener(this); - } + @Override + public void onResume() { + super.onResume(); + eipCommand(Constants.ACTION_CHECK_CERT_VALIDITY); + } - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(IS_EIP_PENDING, EIP.mIsStarting); - } + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putBoolean(IS_PENDING, eip_status.isConnecting()); + outState.putBoolean(IS_CONNECTED, eip_status.isConnected()); + Log.d(TAG, "status message onSaveInstanceState = " + status_message.getText().toString()); + outState.putString(STATUS_MESSAGE, status_message.getText().toString()); + super.onSaveInstanceState(outState); + } protected void saveEipStatus() { boolean eip_is_on = false; - Log.d("bitmask", "saveEipStatus"); + Log.d(TAG, "saveEipStatus"); if(eipSwitch.isChecked()) { eip_is_on = true; } - if(getActivity() != null) + if(parent_activity != null) Dashboard.preferences.edit().putBoolean(Dashboard.START_ON_BOOT, eip_is_on).commit(); } + @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (buttonView.equals(eipSwitch)){ @@ -133,48 +135,46 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe startEipFromScratch(); else if(canLogInToStartEIP()) { Log.d(TAG, "Can Log In to start EIP"); - Dashboard dashboard = (Dashboard) getActivity(); + Dashboard dashboard = (Dashboard) parent_activity; dashboard.logInDialog(Bundle.EMPTY); } } private boolean canStartEIP() { - boolean certificateExists = !Dashboard.preferences.getString(EIP.CERTIFICATE, "").isEmpty(); - boolean isAllowedAnon = Dashboard.preferences.getBoolean(EIP.ALLOWED_ANON, false); - return (isAllowedAnon || certificateExists) && !EIP.mIsStarting && !EIP.isConnected(); + boolean certificateExists = !Dashboard.preferences.getString(Constants.CERTIFICATE, "").isEmpty(); + boolean isAllowedAnon = Dashboard.preferences.getBoolean(Constants.ALLOWED_ANON, false); + return (isAllowedAnon || certificateExists) && !eip_status.isConnected(); } private boolean canLogInToStartEIP() { - boolean isAllowedRegistered = Dashboard.preferences.getBoolean(EIP.ALLOWED_REGISTERED, false); + boolean isAllowedRegistered = Dashboard.preferences.getBoolean(Constants.ALLOWED_REGISTERED, false); boolean isLoggedIn = !LeapSRPSession.getToken().isEmpty(); Log.d(TAG, "Allow registered? " + isAllowedRegistered); Log.d(TAG, "Is logged in? " + isLoggedIn); - return isAllowedRegistered && !isLoggedIn && !EIP.mIsStarting && !EIP.isConnected(); + return isAllowedRegistered && !isLoggedIn && !eip_status.isConnecting() && !eip_status.isConnected(); } private void handleSwitchOff() { - if(EIP.mIsStarting) { + if(eip_status.isConnecting()) { askPendingStartCancellation(); - } else if(EIP.isConnected()) { - Log.d(TAG, "Stopping EIP"); + } else if(eip_status.isConnected()) { stopEIP(); } } private void askPendingStartCancellation() { - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity()); - alertBuilder.setTitle(getResources().getString(R.string.eip_cancel_connect_title)) - .setMessage(getResources().getString(R.string.eip_cancel_connect_text)) + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(parent_activity); + alertBuilder.setTitle(parent_activity.getString(R.string.eip_cancel_connect_title)) + .setMessage(parent_activity.getString(R.string.eip_cancel_connect_text)) .setPositiveButton((R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { stopEIP(); } }) - .setNegativeButton(getResources().getString(R.string.no), new DialogInterface.OnClickListener() { + .setNegativeButton(parent_activity.getString(R.string.no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - Log.d(TAG, "askPendingStartCancellation checks the switch to true"); eipSwitch.setChecked(true); } }) @@ -182,243 +182,185 @@ public class EipServiceFragment extends Fragment implements StateListener, OnChe } public void startEipFromScratch() { - EIP.mIsStarting = true; eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); - String status = getResources().getString(R.string.eip_status_start_pending); - setEipStatus(status); + String status = parent_activity.getString(R.string.eip_status_start_pending); + setStatusMessage(status); if(!eipSwitch.isChecked()) { - Log.d(TAG, "startEipFromScratch checks the switch to true"); eipSwitch.setChecked(true); saveEipStatus(); } - eipCommand(EIP.ACTION_START_EIP); + eipCommand(Constants.ACTION_START_EIP); } protected void stopEIP() { - EIP.mIsStarting = false; - View eipProgressBar = getActivity().findViewById(R.id.eipProgress); + View eipProgressBar = parent_activity.findViewById(R.id.eipProgress); if(eipProgressBar != null) eipProgressBar.setVisibility(View.GONE); - String status = getResources().getString(R.string.eip_state_not_connected); - setEipStatus(status); - eipCommand(EIP.ACTION_STOP_EIP); + String status = parent_activity.getString(R.string.eip_state_not_connected); + setStatusMessage(status); + eipCommand(Constants.ACTION_STOP_EIP); } - /** - * Send a command to EIP - * - * @param action A valid String constant from EIP class representing an Intent - * filter for the EIP class - */ - private void eipCommand(String action){ - // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? - Intent vpn_intent = new Intent(getActivity().getApplicationContext(), EIP.class); - vpn_intent.setAction(action); - vpn_intent.putExtra(EIP.RECEIVER_TAG, mEIPReceiver); - getActivity().startService(vpn_intent); - } + /** + * Send a command to EIP + * + * @param action A valid String constant from EIP class representing an Intent + * filter for the EIP class + */ + private void eipCommand(String action){ + // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? + Intent vpn_intent = new Intent(parent_activity.getApplicationContext(), EIP.class); + vpn_intent.setAction(action); + vpn_intent.putExtra(Constants.RECEIVER_TAG, mEIPReceiver); + parent_activity.startService(vpn_intent); + } @Override - public void updateState(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) { - boolean isNewLevel = EIP.lastConnectionStatusLevel != level; - boolean justDecidedOnDisconnect = EIP.lastConnectionStatusLevel == ConnectionStatus.UNKNOWN_LEVEL; - Log.d(TAG, "update state with level " + level); - if(!justDecidedOnDisconnect && (isNewLevel || level == ConnectionStatus.LEVEL_CONNECTED)) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - EIP.lastConnectionStatusLevel = level; - handleNewState(state, logmessage, localizedResId, level); + public void update (Observable observable, Object data) { + if(observable instanceof EipStatus) { + eip_status = (EipStatus) observable; + final EipStatus eip_status = (EipStatus) observable; + parent_activity.runOnUiThread(new Runnable() { + @Override + public void run() { + handleNewState(eip_status); } }); - } else if(justDecidedOnDisconnect && level == ConnectionStatus.LEVEL_CONNECTED) { - EIP.lastConnectionStatusLevel = ConnectionStatus.LEVEL_NOTCONNECTED; - updateState(state, logmessage, localizedResId, level); - } // else if(isNewLevel || level == ConnectionStatus.LEVEL_AUTH_FAILED) - // handleNewState(state, logmessage, localizedResId, level); + } } - private void handleNewState(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) { - if (level == ConnectionStatus.LEVEL_CONNECTED) + private void handleNewState(EipStatus eip_status) { + Log.d(TAG, "handleNewState: " + eip_status.toString()); + if(eip_status.wantsToDisconnect()) + setDisconnectedUI(); + else if (eip_status.isConnected()) setConnectedUI(); - else if (isDisconnectedLevel(level) && !EIP.mIsStarting) + else if (eip_status.isDisconnected() && !eip_status.isConnecting()) setDisconnectedUI(); - else if (level == ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET) - setNoServerReplyUI(localizedResId, logmessage); - else if (level == ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED) - setServerReplyUI(state, localizedResId, logmessage); - // else if (level == ConnectionStatus.LEVEL_AUTH_FAILED) - // handleSwitchOn(); - } - - private boolean isDisconnectedLevel(final ConnectionStatus level) { - return level == ConnectionStatus.LEVEL_NOTCONNECTED || level == ConnectionStatus.LEVEL_AUTH_FAILED; + else + setInProgressUI(eip_status); } private void setConnectedUI() { hideProgressBar(); - Log.d(TAG, "mIsDisconnecting = false in setConnectedUI"); - EIP.mIsStarting = false; //TODO This should be done in the onReceiveResult from START_EIP command, but right now LaunchVPN isn't notifying anybody the resultcode of the request so we need to listen the states with this listener. - EIP.mIsDisconnecting = false; //TODO See comment above - String status = getString(R.string.eip_state_connected); - setEipStatus(status); + Log.d(TAG, "setConnectedUi? " + eip_status.isConnected()); adjustSwitch(); + setStatusMessage(parent_activity.getString(R.string.eip_state_connected)); } private void setDisconnectedUI(){ hideProgressBar(); - EIP.mIsStarting = false; //TODO See comment in setConnectedUI() - Log.d(TAG, "mIsDisconnecting = false in setDisconnectedUI"); - EIP.mIsDisconnecting = false; //TODO See comment in setConnectedUI() - - String status = getString(R.string.eip_state_not_connected); - setEipStatus(status); adjustSwitch(); + setStatusMessage(parent_activity.getString(R.string.eip_state_not_connected)); } - private void adjustSwitch() { - if(EIP.isConnected()) { + private void adjustSwitch() { + if(eip_status.isConnected() || eip_status.isConnecting()) { + Log.d(TAG, "adjustSwitch, isConnected || isConnecting, is checked? " + eipSwitch.isChecked()); if(!eipSwitch.isChecked()) { eipSwitch.setChecked(true); } } else { + Log.d(TAG, "adjustSwitch, !isConnected && !isConnecting? " + eip_status.toString()); + if(eipSwitch.isChecked()) { eipSwitch.setChecked(false); } } } - private void setNoServerReplyUI(int localizedResId, String logmessage) { - if(eipStatus != null) { - String prefix = getString(localizedResId); - setEipStatus(prefix + " " + logmessage); - } - } - - private void setServerReplyUI(String state, int localizedResId, String logmessage) { - if(eipStatus != null) - if(state.equals("AUTH") || state.equals("GET_CONFIG")) { - String prefix = getString(localizedResId); - setEipStatus(prefix + " " + logmessage); - } + private void setInProgressUI(EipStatus eip_status) { + int localizedResId = eip_status.getLocalizedResId(); + String logmessage = eip_status.getLogMessage(); + String prefix = parent_activity.getString(localizedResId); + + setStatusMessage(prefix + " " + logmessage); + adjustSwitch(); } - protected void setEipStatus(String status) { - if(eipStatus == null) - eipStatus = (TextView) getActivity().findViewById(R.id.eipStatus); - eipStatus.setText(status); + protected void setStatusMessage(String status) { + if(status_message == null) + status_message = (TextView) parent_activity.findViewById(R.id.status_message); + status_message.setText(status); } private void hideProgressBar() { - if(getActivity() != null && getActivity().findViewById(R.id.eipProgress) != null) - getActivity().findViewById(R.id.eipProgress).setVisibility(View.GONE); + if(parent_activity != null && parent_activity.findViewById(R.id.eipProgress) != null) + parent_activity.findViewById(R.id.eipProgress).setVisibility(View.GONE); } - /** - * Inner class for handling messages related to EIP status and control requests - * - * @author Sean Leonard <meanderingcode@aetherislands.net> - */ - protected class EIPReceiver extends ResultReceiver { + protected class EIPReceiver extends ResultReceiver { - protected EIPReceiver(Handler handler){ - super(handler); - } + protected EIPReceiver(Handler handler){ + super(handler); + } - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - super.onReceiveResult(resultCode, resultData); + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + super.onReceiveResult(resultCode, resultData); - String request = resultData.getString(EIP.REQUEST_TAG); - boolean checked = false; - - if (request == EIP.ACTION_IS_EIP_RUNNING) { - switch (resultCode){ - case Activity.RESULT_OK: - checked = true; - break; - case Activity.RESULT_CANCELED: - checked = false; - break; - } - } else if (request == EIP.ACTION_START_EIP) { - switch (resultCode){ - case Activity.RESULT_OK: - Log.d(TAG, "Action start eip = Result OK"); - checked = true; - eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); - EIP.mIsStarting = false; - break; - case Activity.RESULT_CANCELED: - checked = false; - eipFragment.findViewById(R.id.eipProgress).setVisibility(View.GONE); - break; - } - } else if (request == EIP.ACTION_STOP_EIP) { - switch (resultCode){ - case Activity.RESULT_OK: - checked = false; - break; - case Activity.RESULT_CANCELED: - checked = true; - break; - } - } else if (request == EIP.EIP_NOTIFICATION) { - switch (resultCode){ - case Activity.RESULT_OK: - checked = true; - break; - case Activity.RESULT_CANCELED: - checked = false; - break; - } - } else if (request == EIP.ACTION_CHECK_CERT_VALIDITY) { - checked = eipSwitch.isChecked(); - - switch (resultCode) { - case Activity.RESULT_OK: - break; - case Activity.RESULT_CANCELED: - Dashboard dashboard = (Dashboard) getActivity(); - - dashboard.showProgressBar(); - String status = getResources().getString(R.string.updating_certificate_message); - setEipStatus(status); - - if(LeapSRPSession.getToken().isEmpty() && !Dashboard.preferences.getBoolean(EIP.ALLOWED_ANON, false)) { - dashboard.logInDialog(Bundle.EMPTY); - } else { - - Intent provider_API_command = new Intent(getActivity(), ProviderAPI.class); - if (dashboard.providerAPI_result_receiver == null) { - dashboard.providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); - dashboard.providerAPI_result_receiver.setReceiver(dashboard); - } - - provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); - provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, dashboard.providerAPI_result_receiver); - getActivity().startService(provider_API_command); - } - break; - } + String request = resultData.getString(Constants.REQUEST_TAG); + + if (request.equals(Constants.ACTION_START_EIP)) { + switch (resultCode){ + case Activity.RESULT_OK: + Log.d(TAG, "Action start eip = Result OK"); + eipFragment.findViewById(R.id.eipProgress).setVisibility(View.VISIBLE); + break; + case Activity.RESULT_CANCELED: + eipFragment.findViewById(R.id.eipProgress).setVisibility(View.GONE); + break; + } + } else if (request.equals(Constants.ACTION_STOP_EIP)) { + switch (resultCode){ + case Activity.RESULT_OK: + Intent disconnect_vpn = new Intent(parent_activity, DisconnectVPN.class); + parent_activity.startActivityForResult(disconnect_vpn, EIP.DISCONNECT); + eip_status.setDisconnecting(); + break; + case Activity.RESULT_CANCELED: + break; + } + } else if (request.equals(Constants.EIP_NOTIFICATION)) { + switch (resultCode){ + case Activity.RESULT_OK: + break; + case Activity.RESULT_CANCELED: + break; + } + } else if (request.equals(Constants.ACTION_CHECK_CERT_VALIDITY)) { + switch (resultCode) { + case Activity.RESULT_OK: + break; + case Activity.RESULT_CANCELED: + Dashboard dashboard = (Dashboard) parent_activity; + + dashboard.showProgressBar(); + String status = parent_activity.getString(R.string.updating_certificate_message); + setStatusMessage(status); + if(LeapSRPSession.getToken().isEmpty() && !Dashboard.preferences.getBoolean(Constants.ALLOWED_ANON, false)) { + dashboard.logInDialog(Bundle.EMPTY); + } else { + Intent provider_API_command = new Intent(parent_activity, ProviderAPI.class); + if(dashboard.providerAPI_result_receiver == null) { + dashboard.providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); + dashboard.providerAPI_result_receiver.setReceiver(dashboard); } + + provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, dashboard.providerAPI_result_receiver); + parent_activity.startService(provider_API_command); + } + break; } + } } + } public static EIPReceiver getReceiver() { return mEIPReceiver; } - - public static boolean isEipSwitchChecked() { - return eipSwitch.isChecked(); - } - - public void checkEipSwitch(boolean checked) { - eipSwitch.setChecked(checked); - // Log.d(TAG, "checkEipSwitch"); - // onCheckedChanged(eipSwitch, checked); - } } diff --git a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java index a953a710..989dc395 100644 --- a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java +++ b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java @@ -17,13 +17,14 @@ package se.leap.bitmaskclient; +import org.jboss.security.srp.SRPParameters; + import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; -import org.jboss.security.srp.SRPParameters; /** * Implements all SRP algorithm logic. diff --git a/app/src/main/java/se/leap/bitmaskclient/LogInDialog.java b/app/src/main/java/se/leap/bitmaskclient/LogInDialog.java index 5a0c9a6d..5263392e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/LogInDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/LogInDialog.java @@ -16,19 +16,13 @@ */ package se.leap.bitmaskclient; -import se.leap.bitmaskclient.R; -import android.R.color; import android.app.Activity; import android.app.AlertDialog; import android.app.DialogFragment; import android.content.DialogInterface; -import android.content.res.ColorStateList; import android.os.Bundle; -import android.provider.CalendarContract.Colors; import android.view.LayoutInflater; import android.view.View; -import android.view.animation.AlphaAnimation; -import android.view.animation.BounceInterpolator; import android.widget.EditText; import android.widget.TextView; @@ -47,6 +41,8 @@ public class LogInDialog extends SessionDialogInterface { final public static String TAG = LogInDialog.class.getSimpleName(); + private static LogInDialog dialog; + private static boolean is_eip_pending = false; public AlertDialog onCreateDialog(Bundle savedInstanceState) { @@ -55,32 +51,28 @@ public class LogInDialog extends SessionDialogInterface { View log_in_dialog_view = inflater.inflate(R.layout.log_in_dialog, null); final TextView user_message = (TextView)log_in_dialog_view.findViewById(R.id.user_message); - if(getArguments() != null && getArguments().containsKey(getResources().getString(R.string.user_message))) { - user_message.setText(getArguments().getString(getResources().getString(R.string.user_message))); - } else { - user_message.setVisibility(View.GONE); - } - final EditText username_field = (EditText)log_in_dialog_view.findViewById(R.id.username_entered); - if(getArguments() != null && getArguments().containsKey(USERNAME)) { - String username = getArguments().getString(USERNAME); - username_field.setText(username); - } - if (getArguments() != null && getArguments().containsKey(USERNAME_MISSING)) { - username_field.setError(getResources().getString(R.string.username_ask)); - } - final EditText password_field = (EditText)log_in_dialog_view.findViewById(R.id.password_entered); + if(!username_field.getText().toString().isEmpty() && password_field.isFocusable()) { password_field.requestFocus(); } - if (getArguments() != null && getArguments().containsKey(PASSWORD_INVALID_LENGTH)) { - password_field.setError(getResources().getString(R.string.error_not_valid_password_user_message)); - } - if(getArguments() != null && getArguments().getBoolean(EipServiceFragment.IS_EIP_PENDING, false)) { - is_eip_pending = true; + if (getArguments() != null) { + is_eip_pending = getArguments().getBoolean(EipServiceFragment.IS_PENDING, false); + if (getArguments().containsKey(PASSWORD_INVALID_LENGTH)) + password_field.setError(getResources().getString(R.string.error_not_valid_password_user_message)); + if (getArguments().containsKey(USERNAME)) { + String username = getArguments().getString(USERNAME); + username_field.setText(username); } - + if (getArguments().containsKey(USERNAME_MISSING)) { + username_field.setError(getResources().getString(R.string.username_ask)); + } + if(getArguments().containsKey(getResources().getString(R.string.user_message))) + user_message.setText(getArguments().getString(getResources().getString(R.string.user_message))); + else + user_message.setVisibility(View.GONE); + } builder.setView(log_in_dialog_view) .setPositiveButton(R.string.login_button, new DialogInterface.OnClickListener() { @@ -116,7 +108,6 @@ public class LogInDialog extends SessionDialogInterface { */ public interface LogInDialogInterface { public void logIn(String username, String password); - public void cancelAuthedEipOn(); public void signUp(String username, String password); public void cancelLoginOrSignup(); } @@ -127,8 +118,10 @@ public class LogInDialog extends SessionDialogInterface { * @return a new instance of this DialogFragment. */ public static DialogFragment newInstance() { - LogInDialog dialog_fragment = new LogInDialog(); - return dialog_fragment; + if(dialog == null) + dialog = new LogInDialog(); + + return dialog; } @Override @@ -146,6 +139,6 @@ public class LogInDialog extends SessionDialogInterface { public void onCancel(DialogInterface dialog) { super.onCancel(dialog); if(is_eip_pending) - interface_with_Dashboard.cancelAuthedEipOn(); + interface_with_Dashboard.cancelLoginOrSignup(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java index eb196d46..07ed6c8f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java +++ b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java @@ -3,8 +3,8 @@ package se.leap.bitmaskclient; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.util.Log; +import se.leap.bitmaskclient.eip.Constants; public class OnBootReceiver extends BroadcastReceiver { @@ -14,7 +14,7 @@ public class OnBootReceiver extends BroadcastReceiver { if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { if (!context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE).getString(Provider.KEY, "").isEmpty() && context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE).getBoolean(Dashboard.START_ON_BOOT, false)) { Intent dashboard_intent = new Intent(context, Dashboard.class); - dashboard_intent.setAction(EIP.ACTION_START_EIP); + dashboard_intent.setAction(Constants.ACTION_START_EIP); dashboard_intent.putExtra(Dashboard.ON_BOOT, true); dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(dashboard_intent); diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java index 8d6385e0..fa1a4fb5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -16,17 +16,17 @@ */ package se.leap.bitmaskclient; -import java.io.Serializable; -import java.util.Arrays; -import java.util.Locale; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import android.content.Context; -import android.app.Activity; -import android.content.SharedPreferences; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Locale; /** * @author Sean Leonard <meanderingcode@aetherislands.net> diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java index 43bba085..1148e65e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListAdapter.java @@ -1,7 +1,5 @@ package se.leap.bitmaskclient; -import java.util.List; - import android.content.Context; import android.view.LayoutInflater; import android.view.View; @@ -9,6 +7,8 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TwoLineListItem; +import java.util.List; + public class ProviderListAdapter<T> extends ArrayAdapter<T> { private static boolean[] hidden = null; diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java index db414d87..e5baebc0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListFragment.java @@ -16,8 +16,6 @@ */
package se.leap.bitmaskclient;
-import se.leap.bitmaskclient.R;
-import se.leap.bitmaskclient.ProviderListContent.ProviderItem;
import android.app.Activity;
import android.app.ListFragment;
import android.os.Bundle;
@@ -26,11 +24,12 @@ import android.view.View; import android.view.ViewGroup;
import android.widget.ListView;
+import se.leap.bitmaskclient.ProviderListContent.ProviderItem;
+
/**
* A list fragment representing a list of Providers. This fragment
* also supports tablet devices by allowing list items to be given an
- * 'activated' state upon selection. This helps indicate which item is
- * currently being viewed in a {@link DashboardFragment}.
+ * 'activated' state upon selection.
* <p>
* Activities containing this fragment MUST implement the {@link Callbacks}
* interface.
@@ -123,7 +122,7 @@ public class ProviderListFragment extends ListFragment { if(getArguments() != null && getArguments().containsKey(TOP_PADDING)) {
int topPadding = getArguments().getInt(TOP_PADDING);
View current_view = getView();
- getView().setPadding(current_view.getPaddingLeft(), topPadding, current_view.getPaddingRight(), current_view.getPaddingBottom());
+ current_view.setPadding(current_view.getPaddingLeft(), topPadding, current_view.getPaddingRight(), current_view.getPaddingBottom());
}
}
@@ -215,7 +214,7 @@ public class ProviderListFragment extends ListFragment { real_count--;
} else {
i++;
- } + }
}
public void unhideAll() {
diff --git a/app/src/main/java/se/leap/bitmaskclient/SessionDialogInterface.java b/app/src/main/java/se/leap/bitmaskclient/SessionDialogInterface.java index 7b08a4d1..66b86ccd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/SessionDialogInterface.java +++ b/app/src/main/java/se/leap/bitmaskclient/SessionDialogInterface.java @@ -17,10 +17,8 @@ package se.leap.bitmaskclient; import android.app.Activity; -import android.app.AlertDialog; import android.app.DialogFragment; import android.content.DialogInterface; -import android.os.Bundle; /** * @author parmegv diff --git a/app/src/main/java/se/leap/bitmaskclient/SignUpDialog.java b/app/src/main/java/se/leap/bitmaskclient/SignUpDialog.java index 3cb41f4f..f6d6cc3f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/SignUpDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/SignUpDialog.java @@ -16,19 +16,13 @@ */ package se.leap.bitmaskclient; -import se.leap.bitmaskclient.R; -import android.R.color; import android.app.Activity; import android.app.AlertDialog; import android.app.DialogFragment; import android.content.DialogInterface; -import android.content.res.ColorStateList; import android.os.Bundle; -import android.provider.CalendarContract.Colors; import android.view.LayoutInflater; import android.view.View; -import android.view.animation.AlphaAnimation; -import android.view.animation.BounceInterpolator; import android.widget.EditText; import android.widget.TextView; @@ -46,6 +40,7 @@ public class SignUpDialog extends SessionDialogInterface { final public static String TAG = SignUpDialog.class.getSimpleName(); + private static SignUpDialog dialog; private static boolean is_eip_pending = false; public AlertDialog onCreateDialog(Bundle savedInstanceState) { @@ -54,32 +49,27 @@ public class SignUpDialog extends SessionDialogInterface { View log_in_dialog_view = inflater.inflate(R.layout.log_in_dialog, null); final TextView user_message = (TextView)log_in_dialog_view.findViewById(R.id.user_message); - if(getArguments() != null && getArguments().containsKey(getResources().getString(R.string.user_message))) { - user_message.setText(getArguments().getString(getResources().getString(R.string.user_message))); - } else { - user_message.setVisibility(View.GONE); - } - final EditText username_field = (EditText)log_in_dialog_view.findViewById(R.id.username_entered); - if(getArguments() != null && getArguments().containsKey(USERNAME)) { - String username = getArguments().getString(USERNAME); - username_field.setText(username); - } - if (getArguments() != null && getArguments().containsKey(USERNAME_MISSING)) { - username_field.setError(getResources().getString(R.string.username_ask)); - } - final EditText password_field = (EditText)log_in_dialog_view.findViewById(R.id.password_entered); + if(!username_field.getText().toString().isEmpty() && password_field.isFocusable()) { password_field.requestFocus(); } - if (getArguments() != null && getArguments().containsKey(PASSWORD_INVALID_LENGTH)) { - password_field.setError(getResources().getString(R.string.error_not_valid_password_user_message)); - } - if(getArguments() != null && getArguments().getBoolean(EipServiceFragment.IS_EIP_PENDING, false)) { - is_eip_pending = true; + if (getArguments() != null) { + is_eip_pending = getArguments().getBoolean(EipServiceFragment.IS_PENDING, false); + if (getArguments().containsKey(PASSWORD_INVALID_LENGTH)) + password_field.setError(getResources().getString(R.string.error_not_valid_password_user_message)); + if(getArguments().containsKey(USERNAME_MISSING)) + username_field.setError(getResources().getString(R.string.username_ask)); + if(getArguments().containsKey(USERNAME)) { + String username = getArguments().getString(USERNAME); + username_field.setText(username); } - + if(getArguments().containsKey(getResources().getString(R.string.user_message))) + user_message.setText(getArguments().getString(getResources().getString(R.string.user_message))); + else + user_message.setVisibility(View.GONE); + } builder.setView(log_in_dialog_view) .setPositiveButton(R.string.signup_button, new DialogInterface.OnClickListener() { @@ -108,7 +98,6 @@ public class SignUpDialog extends SessionDialogInterface { */ public interface SignUpDialogInterface { public void signUp(String username, String password); - public void cancelAuthedEipOn(); public void cancelLoginOrSignup(); } @@ -118,8 +107,9 @@ public class SignUpDialog extends SessionDialogInterface { * @return a new instance of this DialogFragment. */ public static DialogFragment newInstance() { - SignUpDialog dialog_fragment = new SignUpDialog(); - return dialog_fragment; + if(dialog == null) + dialog = new SignUpDialog(); + return dialog; } @Override @@ -136,7 +126,7 @@ public class SignUpDialog extends SessionDialogInterface { @Override public void onCancel(DialogInterface dialog) { if(is_eip_pending) - interface_with_Dashboard.cancelAuthedEipOn(); + interface_with_Dashboard.cancelLoginOrSignup(); super.onCancel(dialog); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java b/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java new file mode 100644 index 00000000..e1a7e616 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package se.leap.bitmaskclient.eip; + +/** + * + * Constants for intent passing, shared preferences + * + * @author Parménides GV <parmegv@sdf.org> + * + */ +public interface Constants { + + public final static String TAG = Constants.class.getSimpleName(); + + public final static String AUTHED_EIP = TAG + ".AUTHED_EIP"; + public final static String ACTION_CHECK_CERT_VALIDITY = TAG + ".CHECK_CERT_VALIDITY"; + public final static String ACTION_START_EIP = TAG + ".START_EIP"; + public final static String ACTION_STOP_EIP = TAG + ".STOP_EIP"; + public final static String ACTION_UPDATE_EIP_SERVICE = TAG + ".UPDATE_EIP_SERVICE"; + public final static String ACTION_IS_EIP_RUNNING = TAG + ".IS_RUNNING"; + public final static String EIP_NOTIFICATION = TAG + ".EIP_NOTIFICATION"; + public final static String ALLOWED_ANON = "allow_anonymous"; + public final static String ALLOWED_REGISTERED = "allow_registration"; + public final static String CERTIFICATE = "cert"; + public final static String PRIVATE_KEY = TAG + ".PRIVATE_KEY"; + public final static String KEY = TAG + ".KEY"; + public final static String PARSED_SERIAL = TAG + ".PARSED_SERIAL"; + public final static String RECEIVER_TAG = TAG + ".RECEIVER_TAG"; + public final static String REQUEST_TAG = TAG + ".REQUEST_TAG"; + public final static String START_BLOCKING_VPN_PROFILE = TAG + ".START_BLOCKING_VPN_PROFILE"; + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java new file mode 100644 index 00000000..b4208556 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -0,0 +1,255 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package se.leap.bitmaskclient.eip; + +import android.app.Activity; +import android.app.IntentService; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import de.blinkt.openvpn.LaunchVPN; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ProfileManager; +import se.leap.bitmaskclient.Dashboard; +import se.leap.bitmaskclient.EipServiceFragment; +import se.leap.bitmaskclient.Provider; + +import static se.leap.bitmaskclient.eip.Constants.ACTION_CHECK_CERT_VALIDITY; +import static se.leap.bitmaskclient.eip.Constants.ACTION_IS_EIP_RUNNING; +import static se.leap.bitmaskclient.eip.Constants.ACTION_START_EIP; +import static se.leap.bitmaskclient.eip.Constants.ACTION_STOP_EIP; +import static se.leap.bitmaskclient.eip.Constants.ACTION_UPDATE_EIP_SERVICE; +import static se.leap.bitmaskclient.eip.Constants.CERTIFICATE; +import static se.leap.bitmaskclient.eip.Constants.KEY; +import static se.leap.bitmaskclient.eip.Constants.PARSED_SERIAL; +import static se.leap.bitmaskclient.eip.Constants.RECEIVER_TAG; +import static se.leap.bitmaskclient.eip.Constants.REQUEST_TAG; + +/** + * EIP is the abstract base class for interacting with and managing the Encrypted + * Internet Proxy connection. Connections are started, stopped, and queried through + * this IntentService. + * Contains logic for parsing eip-service.json from the provider, configuring and selecting + * gateways, and controlling {@link de.blinkt.openvpn.core.OpenVPNService} connections. + * + * @author Sean Leonard <meanderingcode@aetherislands.net> + * @author Parménides GV <parmegv@sdf.org> + */ +public final class EIP extends IntentService { + + public final static String TAG = EIP.class.getSimpleName(); + public final static String SERVICE_API_PATH = "config/eip-service.json"; + + + public static final int DISCONNECT = 15; + + private static Context context; + private static ResultReceiver mReceiver; + private static SharedPreferences preferences; + + private static JSONObject eip_definition = null; + private static List<Gateway> gateways = new ArrayList<Gateway>(); + private static ProfileManager profile_manager; + private static Gateway activeGateway = null; + + public EIP(){ + super("LEAPEIP"); + } + + @Override + public void onCreate() { + super.onCreate(); + + context = getApplicationContext(); + profile_manager = ProfileManager.getInstance(context); + + preferences = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE); + refreshEipDefinition(); + } + + @Override + protected void onHandleIntent(Intent intent) { + String action = intent.getAction(); + mReceiver = intent.getParcelableExtra(RECEIVER_TAG); + + if ( action.equals(ACTION_START_EIP)) + startEIP(); + else if (action.equals(ACTION_STOP_EIP)) + stopEIP(); + else if (action.equals(ACTION_IS_EIP_RUNNING)) + isRunning(); + else if (action.equals(ACTION_UPDATE_EIP_SERVICE)) + updateEIPService(); + else if (action.equals(ACTION_CHECK_CERT_VALIDITY)) + checkCertValidity(); + } + + /** + * Initiates an EIP connection by selecting a gateway and preparing and sending an + * Intent to {@link de.blinkt.openvpn.LaunchVPN}. + * It also sets up early routes. + */ + private void startEIP() { + if(gateways.isEmpty()) + updateEIPService(); + GatewaySelector gateway_selector = new GatewaySelector(gateways); + activeGateway = gateway_selector.select(); + if(activeGateway != null && activeGateway.getProfile() != null) { + mReceiver = EipServiceFragment.getReceiver(); + launchActiveGateway(); + } + earlyRoutes(); + } + + /** + * Early routes are routes that block traffic until a new + * VpnService is started properly. + */ + private void earlyRoutes() { + Intent void_vpn_launcher = new Intent(context, VoidVpnLauncher.class); + void_vpn_launcher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(void_vpn_launcher); + } + + private void launchActiveGateway() { + Intent intent = new Intent(this,LaunchVPN.class); + intent.setAction(Intent.ACTION_MAIN); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(LaunchVPN.EXTRA_NAME, activeGateway.getProfile().getName() ); + intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); + intent.putExtra(RECEIVER_TAG, mReceiver); + startActivity(intent); + } + + /** + * Disconnects the EIP connection gracefully through the bound service or forcefully + * if there is no bound service. Sends a message to the requesting ResultReceiver. + */ + private void stopEIP() { + EipStatus eip_status = EipStatus.getInstance(); + Log.d(TAG, "stopEip(): eip is connected? " + eip_status.isConnected()); + int result_code = Activity.RESULT_CANCELED; + if(eip_status.isConnected()) + result_code = Activity.RESULT_OK; + + tellToReceiver(ACTION_STOP_EIP, result_code); + } + + /** + * Checks the last stored status notified by ics-openvpn + * Sends <code>Activity.RESULT_CANCELED</code> to the ResultReceiver that made the + * request if it's not connected, <code>Activity.RESULT_OK</code> otherwise. + */ + private void isRunning() { + EipStatus eip_status = EipStatus.getInstance(); + int resultCode = (eip_status.isConnected()) ? + Activity.RESULT_OK : + Activity.RESULT_CANCELED; + tellToReceiver(ACTION_IS_EIP_RUNNING, resultCode); + } + + /** + * Loads eip-service.json from SharedPreferences, delete previous vpn profiles and add new gateways. + * TODO Implement API call to refresh eip-service.json from the provider + */ + private void updateEIPService() { + refreshEipDefinition(); + deleteAllVpnProfiles(); + updateGateways(); + tellToReceiver(ACTION_UPDATE_EIP_SERVICE, Activity.RESULT_OK); + } + + private void refreshEipDefinition() { + try { + String eip_definition_string = preferences.getString(KEY, ""); + if(!eip_definition_string.isEmpty()) { + eip_definition = new JSONObject(eip_definition_string); + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private void deleteAllVpnProfiles() { + Collection<VpnProfile> profiles = profile_manager.getProfiles(); + profiles.removeAll(profiles); + } + + /** + * Walk the list of gateways defined in eip-service.json and parse them into + * Gateway objects. + * TODO Store the Gateways (as Serializable) in SharedPreferences + */ + private void updateGateways(){ + try { + JSONArray gatewaysDefined = eip_definition.getJSONArray("gateways"); + for ( int i=0 ; i < gatewaysDefined.length(); i++ ){ + JSONObject gw = gatewaysDefined.getJSONObject(i); + if(isOpenVpnGateway(gw)) { + addGateway(new Gateway(eip_definition, context, gw)); + } + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + preferences.edit().putInt(PARSED_SERIAL, eip_definition.optInt(Provider.API_RETURN_SERIAL)).apply(); + } + + private boolean isOpenVpnGateway(JSONObject gateway) { + try { + String transport = gateway.getJSONObject("capabilities").getJSONArray("transport").toString(); + return transport.contains("openvpn"); + } catch (JSONException e) { + return false; + } + } + + private void addGateway(Gateway gateway) { + profile_manager.addProfile(gateway.getProfile()); + gateways.add(gateway); + } + + private void checkCertValidity() { + VpnCertificateValidator validator = new VpnCertificateValidator(); + int resultCode = validator.isValid(preferences.getString(CERTIFICATE, "")) ? + Activity.RESULT_OK : + Activity.RESULT_CANCELED; + tellToReceiver(ACTION_CHECK_CERT_VALIDITY, resultCode); + } + + private void tellToReceiver(String action, int resultCode) { + if (mReceiver != null){ + Bundle resultData = new Bundle(); + resultData.putString(REQUEST_TAG, action); + mReceiver.send(resultCode, resultData); + } + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java new file mode 100644 index 00000000..4ac3bd6a --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package se.leap.bitmaskclient.eip; + +import android.util.Log; + +import java.util.Observable; + +import de.blinkt.openvpn.core.VpnStatus; + +public class EipStatus extends Observable implements VpnStatus.StateListener { + public static String TAG = EipStatus.class.getSimpleName(); + private static EipStatus current_status; + + private static VpnStatus.ConnectionStatus level = VpnStatus.ConnectionStatus.LEVEL_NOTCONNECTED; + private static boolean wants_to_disconnect = false; + + private String state, log_message; + private int localized_res_id; + + public static EipStatus getInstance() { + if(current_status == null) { + current_status = new EipStatus(); + VpnStatus.addStateListener(current_status); + } + return current_status; + } + + private EipStatus() { } + + @Override + public void updateState(final String state, final String logmessage, final int localizedResId, final VpnStatus.ConnectionStatus level) { + current_status = getInstance(); + current_status.setState(state); + current_status.setLogMessage(logmessage); + current_status.setLocalizedResId(localizedResId); + current_status.setLevel(level); + current_status.setChanged(); + if(isConnected() || isDisconnected()) + setConnectedOrDisconnected(); + else if(isConnecting()) + setConnecting(); + Log.d(TAG, "update state with level " + level); + current_status.notifyObservers(); + } + + public boolean wantsToDisconnect() { + return wants_to_disconnect; + } + + public boolean isConnecting() { + return + !isConnected() && + !isDisconnected() && + !isPaused(); + } + + public boolean isConnected() { + return level == VpnStatus.ConnectionStatus.LEVEL_CONNECTED; + } + + public boolean isDisconnected() { + return level == VpnStatus.ConnectionStatus.LEVEL_NOTCONNECTED; + } + + public boolean isPaused() { + return level == VpnStatus.ConnectionStatus.LEVEL_VPNPAUSED; + } + + public void setConnecting() { + wants_to_disconnect = false; + current_status.setChanged(); + current_status.notifyObservers(); + } + + public void setConnectedOrDisconnected() { + Log.d(TAG, "setConnectedOrDisconnected()"); + wants_to_disconnect = false; + current_status.setChanged(); + current_status.notifyObservers(); + } + + public void setDisconnecting() { + wants_to_disconnect = false; + } + + public String getState() { + return state; + } + + public String getLogMessage() { + return log_message; + } + + public int getLocalizedResId() { + return localized_res_id; + } + + public VpnStatus.ConnectionStatus getLevel() { + return level; + } + + private void setState(String state) { + this.state = state; + } + + private void setLogMessage(String log_message) { + this.log_message = log_message; + } + + private void setLocalizedResId(int localized_res_id) { + this.localized_res_id = localized_res_id; + } + + private void setLevel(VpnStatus.ConnectionStatus level) { + EipStatus.level = level; + } + + @Override + public String toString() { + return "State: " + state + " Level: " + level.toString(); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java new file mode 100644 index 00000000..3ee9443c --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package se.leap.bitmaskclient.eip; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Collection; +import java.util.Iterator; + +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ConfigParser; +import de.blinkt.openvpn.core.ProfileManager; +import se.leap.bitmaskclient.Dashboard; + +/** + * Gateway provides objects defining gateways and their metadata. + * Each instance contains a VpnProfile for OpenVPN specific data and member + * variables describing capabilities and location (name) + * + * @author Sean Leonard <meanderingcode@aetherislands.net> + * @author Parménides GV <parmegv@sdf.org> + */ +public class Gateway { + + private String TAG = Gateway.class.getSimpleName(); + + private String mName; + private int timezone; + private JSONObject general_configuration; + private Context context; + private VpnProfile mVpnProfile; + private JSONObject mGateway; + + /** + * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json + * and create a VpnProfile belonging to it. + * + * @param gateway The JSON OpenVPN gateway definition to parse + */ + protected Gateway(JSONObject eip_definition, Context context, JSONObject gateway){ + + mGateway = gateway; + + this.context = context; + general_configuration = getGeneralConfiguration(eip_definition); + timezone = getTimezone(eip_definition); + mName = locationAsName(eip_definition); + + // Currently deletes VpnProfile for host, if there already is one, and builds new + ProfileManager vpl = ProfileManager.getInstance(context); + Collection<VpnProfile> profiles = vpl.getProfiles(); + for (Iterator<VpnProfile> it = profiles.iterator(); it.hasNext(); ){ + VpnProfile p = it.next(); + + if ( p.mName.equalsIgnoreCase( mName ) ) { + it.remove(); + vpl.removeProfile(context, p); + } + } + + mVpnProfile = createVPNProfile(); + mVpnProfile.mName = mName; + + vpl.addProfile(mVpnProfile); + vpl.saveProfile(context, mVpnProfile); + vpl.saveProfileList(context); + } + + private JSONObject getGeneralConfiguration(JSONObject eip_definition) { + try { + return eip_definition.getJSONObject("openvpn_configuration"); + } catch (JSONException e) { + return new JSONObject(); + } + } + + private int getTimezone(JSONObject eip_definition) { + JSONObject location = getLocationInfo(eip_definition); + return location.optInt("timezone"); + } + + private String locationAsName(JSONObject eip_definition) { + JSONObject location = getLocationInfo(eip_definition); + return location.optString("name"); + } + + private JSONObject getLocationInfo(JSONObject eip_definition) { + try { + JSONObject locations = eip_definition.getJSONObject("locations"); + + return locations.getJSONObject(mGateway.getString("location")); + } catch (JSONException e) { + return new JSONObject(); + } + } + + /** + * Create and attach the VpnProfile to our gateway object + */ + private VpnProfile createVPNProfile(){ + try { + ConfigParser cp = new ConfigParser(); + + SharedPreferences preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Activity.MODE_PRIVATE); + VpnConfigGenerator vpn_configuration_generator = new VpnConfigGenerator(preferences, general_configuration, mGateway); + String configuration = vpn_configuration_generator.generate(); + + cp.parseConfig(new StringReader(configuration)); + return cp.convertProfile(); + } catch (ConfigParser.ConfigParseError e) { + // FIXME We didn't get a VpnProfile! Error handling! and log level + Log.v(TAG,"Error creating VPNProfile"); + e.printStackTrace(); + return null; + } catch (IOException e) { + // FIXME We didn't get a VpnProfile! Error handling! and log level + Log.v(TAG,"Error creating VPNProfile"); + e.printStackTrace(); + return null; + } + } + + public String getName() { + return mName; + } + + public VpnProfile getProfile() { + return mVpnProfile; + } + + public int getTimezone() { + return timezone; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java new file mode 100644 index 00000000..39ae7ca6 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java @@ -0,0 +1,46 @@ +package se.leap.bitmaskclient.eip; + +import java.util.Calendar; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeMap; + +public class GatewaySelector { + List<Gateway> gateways; + + public GatewaySelector(List<Gateway> gateways) { + this.gateways = gateways; + } + + public Gateway select() { + return closestGateway(); + } + + private Gateway closestGateway() { + TreeMap<Integer, Set<Gateway>> offsets = calculateOffsets(); + return offsets.isEmpty() ? null : offsets.firstEntry().getValue().iterator().next(); + } + + private TreeMap<Integer, Set<Gateway>> calculateOffsets() { + TreeMap<Integer, Set<Gateway>> offsets = new TreeMap<Integer, Set<Gateway>>(); + int localOffset = Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 3600000; + for(Gateway gateway : gateways) { + int dist = timezoneDistance(localOffset, gateway.getTimezone()); + Set<Gateway> set = (offsets.get(dist) != null) ? + offsets.get(dist) : new HashSet<Gateway>(); + set.add(gateway); + offsets.put(dist, set); + } + return offsets; + } + + private int timezoneDistance(int local_timezone, int remote_timezone) { + // Distance along the numberline of Prime Meridian centric, assumes UTC-11 through UTC+12 + int dist = Math.abs(local_timezone - remote_timezone); + // Farther than 12 timezones and it's shorter around the "back" + if (dist > 12) + dist = 12 - (dist -12); // Well i'll be. Absolute values make equations do funny things. + return dist; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/VoidVpnLauncher.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java index 3b286fbf..d79d8003 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VoidVpnLauncher.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java @@ -1,4 +1,4 @@ -package se.leap.bitmaskclient; +package se.leap.bitmaskclient.eip; import android.app.Activity; import android.content.Intent; @@ -8,7 +8,7 @@ import android.os.Bundle; public class VoidVpnLauncher extends Activity { private static final int VPN_USER_PERMISSION = 71; - + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -28,7 +28,7 @@ public class VoidVpnLauncher extends Activity { if(requestCode == VPN_USER_PERMISSION) { if(resultCode == RESULT_OK) { Intent void_vpn_service = new Intent(getApplicationContext(), VoidVpnService.class); - void_vpn_service.setAction(VoidVpnService.START_BLOCKING_VPN_PROFILE); + void_vpn_service.setAction(Constants.START_BLOCKING_VPN_PROFILE); startService(void_vpn_service); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/VoidVpnService.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java index 7b597554..a6f9fe76 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VoidVpnService.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java @@ -1,19 +1,16 @@ -package se.leap.bitmaskclient; +package se.leap.bitmaskclient.eip; import android.content.Intent; -import android.os.Process; import android.net.VpnService; -import android.util.Log; public class VoidVpnService extends VpnService { - static final String START_BLOCKING_VPN_PROFILE = "se.leap.bitmaskclient.START_BLOCKING_VPN_PROFILE"; static final String TAG = VoidVpnService.class.getSimpleName(); @Override public int onStartCommand(Intent intent, int flags, int startId) { String action = intent.getAction(); - if (action == START_BLOCKING_VPN_PROFILE) { + if (action == Constants.START_BLOCKING_VPN_PROFILE) { new Thread(new Runnable() { public void run() { Builder builder = new Builder(); diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java new file mode 100644 index 00000000..6487f6c1 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2013 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package se.leap.bitmaskclient.eip; + +import android.util.Log; + +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Calendar; + +import se.leap.bitmaskclient.ConfigHelper; + +public class VpnCertificateValidator { + public final static String TAG = VpnCertificateValidator.class.getSimpleName(); + + public boolean isValid(String certificate) { + if(!certificate.isEmpty()) { + X509Certificate certificate_x509 = ConfigHelper.parseX509CertificateFromString(certificate); + return isValid(certificate_x509); + } else return true; + } + + private boolean isValid(X509Certificate certificate) { + Calendar offset_date = calculateOffsetCertificateValidity(certificate); + try { + Log.d(TAG, "offset_date = " + offset_date.getTime().toString()); + certificate.checkValidity(offset_date.getTime()); + return true; + } catch(CertificateExpiredException e) { + return false; + } catch(CertificateNotYetValidException e) { + return false; + } + } + + private Calendar calculateOffsetCertificateValidity(X509Certificate certificate) { + Log.d(TAG, "certificate not after = " + certificate.getNotAfter()); + long preventive_time = Math.abs(certificate.getNotBefore().getTime() - certificate.getNotAfter().getTime())/2; + long current_date_millis = Calendar.getInstance().getTimeInMillis(); + + Calendar limit_date = Calendar.getInstance(); + limit_date.setTimeInMillis(current_date_millis + preventive_time); + return limit_date; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java index ef049a3c..0c8e9a04 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -14,18 +14,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -package se.leap.bitmaskclient; +package se.leap.bitmaskclient.eip; import android.content.SharedPreferences; import android.util.Log; -import java.util.Iterator; -import java.util.Vector; + import org.json.JSONArray; -import org.json.JSONObject; import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Iterator; import se.leap.bitmaskclient.Provider; -import se.leap.bitmaskclient.EIP; public class VpnConfigGenerator { @@ -39,7 +39,7 @@ public class VpnConfigGenerator { public VpnConfigGenerator(SharedPreferences preferences, JSONObject general_configuration, JSONObject gateway) { this.general_configuration = general_configuration; this.gateway = gateway; - this.preferences = preferences; + VpnConfigGenerator.preferences = preferences; } public String generate() { @@ -57,7 +57,6 @@ public class VpnConfigGenerator { String common_options = ""; try { Iterator keys = general_configuration.keys(); - Vector<Vector<String>> value = new Vector<Vector<String>>(); while ( keys.hasNext() ){ String key = keys.next().toString(); @@ -121,14 +120,14 @@ public class VpnConfigGenerator { String key = "<key>" + new_line - + preferences.getString(EIP.PRIVATE_KEY, "") + + preferences.getString(Constants.PRIVATE_KEY, "") + new_line + "</key>"; String openvpn_cert = "<cert>" + new_line - + preferences.getString(EIP.CERTIFICATE, "") + + preferences.getString(Constants.CERTIFICATE, "") + new_line + "</cert>"; diff --git a/app/src/main/res/layout-xlarge/eip_service_fragment.xml b/app/src/main/res/layout-xlarge/eip_service_fragment.xml index e5c7f23d..d217e1a1 100644 --- a/app/src/main/res/layout-xlarge/eip_service_fragment.xml +++ b/app/src/main/res/layout-xlarge/eip_service_fragment.xml @@ -61,13 +61,13 @@ android:src="@drawable/ic_sysbar_quicksettings" /> <TextView - android:id="@+id/eipStatus" + android:id="@+id/status_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:clickable="true" - android:text="@string/status_unknown" + android:text="@string/eip_state_not_connected" android:textSize="16sp" /> </RelativeLayout> diff --git a/app/src/main/res/layout/eip_service_fragment.xml b/app/src/main/res/layout/eip_service_fragment.xml index 5992a873..be2aa791 100644 --- a/app/src/main/res/layout/eip_service_fragment.xml +++ b/app/src/main/res/layout/eip_service_fragment.xml @@ -58,13 +58,13 @@ android:src="@drawable/ic_sysbar_quicksettings" /> <TextView - android:id="@+id/eipStatus" + android:id="@+id/status_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:clickable="true" - android:text="@string/status_unknown" /> + android:text="@string/eip_state_not_connected" /> </RelativeLayout> |