diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/src/main/AndroidManifest.xml | 10 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java | 5 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/EIP.java | 307 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/VoidVpnLauncher.java | 37 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/VoidVpnService.java | 42 |
5 files changed, 255 insertions, 146 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 423293b6..68a99560 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,6 +36,14 @@ android:label="@string/app" > <service + android:name="se.leap.bitmaskclient.VoidVpnService" + android:permission="android.permission.BIND_VPN_SERVICE"> + <intent-filter> + <action android:name="android.net.VpnService" /> + </intent-filter> + </service> + + <service android:name="de.blinkt.openvpn.core.OpenVPNService" android:permission="android.permission.BIND_VPN_SERVICE"> <intent-filter> @@ -53,6 +61,8 @@ </intent-filter> </receiver> + <activity + android:name="se.leap.bitmaskclient.VoidVpnLauncher" /> <activity android:theme="@android:style/Theme.DeviceDefault.Light.Dialog" android:name="de.blinkt.openvpn.activities.DisconnectVPN" /> diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java index a8bd3b7a..c95d0c8b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java @@ -18,8 +18,9 @@ package se.leap.bitmaskclient; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.math.BigInteger; import java.io.InputStream; +import java.math.BigInteger; +import java.lang.IllegalArgumentException; import java.security.KeyFactory; import java.security.KeyStore; import java.security.KeyStoreException; @@ -105,6 +106,8 @@ public class ConfigHelper { e.printStackTrace(); } catch (IOException e) { return null; + } catch (IllegalArgumentException e) { + return null; } return (X509Certificate) certificate; diff --git a/app/src/main/java/se/leap/bitmaskclient/EIP.java b/app/src/main/java/se/leap/bitmaskclient/EIP.java index 43fe0b7c..4a8bae46 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/EIP.java @@ -16,35 +16,23 @@ */ package se.leap.bitmaskclient; - - - - import android.app.Activity; import android.app.IntentService; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.SharedPreferences; -import android.drm.DrmStore.Action; import android.os.Bundle; -import android.os.IBinder; 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.ConfigParseError; import de.blinkt.openvpn.core.ConfigParser; -import de.blinkt.openvpn.core.OpenVpnManagementThread; -import de.blinkt.openvpn.core.OpenVPNService.LocalBinder; -import de.blinkt.openvpn.core.OpenVPNService; +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.lang.StringBuffer; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; @@ -106,8 +94,6 @@ public final class EIP extends IntentService { private static Context context; private static ResultReceiver mReceiver; private static boolean mBound = false; - // Used to store actions to "resume" onServiceConnection - private static String mPending = null; private static int parsedEipSerial; private static JSONObject eipDefinition = null; @@ -140,59 +126,136 @@ public final class EIP extends IntentService { } - @Override - protected void onHandleIntent(Intent intent) { - String action = intent.getAction(); - mReceiver = intent.getParcelableExtra(RECEIVER_TAG); + @Override + protected void onHandleIntent(Intent intent) { + String action = intent.getAction(); + mReceiver = intent.getParcelableExtra(RECEIVER_TAG); - if ( action == ACTION_IS_EIP_RUNNING ) - this.isRunning(); - if ( action == ACTION_UPDATE_EIP_SERVICE ) - this.updateEIPService(); - else if ( action == ACTION_START_EIP ) - this.startEIP(); - else if ( action == ACTION_STOP_EIP ) - this.stopEIP(); - else if ( action == ACTION_CHECK_CERT_VALIDITY ) - this.checkCertValidity(); - else if ( action == ACTION_REBUILD_PROFILES ) { - this.updateGateways(); - } - } + 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(); + } - /** - * 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. - */ + /** + * 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) { + 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>>(); - private void isRunning() { - Bundle resultData = new Bundle(); - resultData.putString(REQUEST_TAG, ACTION_IS_EIP_RUNNING); - int resultCode = Activity.RESULT_CANCELED; - boolean is_connected = isConnected(); - - resultCode = (is_connected) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; - - if (mReceiver != null){ - mReceiver.send(resultCode, resultData); - } - - Log.d(TAG, "isRunning() = " + is_connected); - } + int localOffset = Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 3600000; - /** - * Initiates an EIP connection by selecting a gateway and preparing and sending an - * Intent to {@link se.leap.openvpn.LaunchVPN} - */ - private void startEIP() { - activeGateway = selectGateway(); + 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>(); - if(activeGateway != null && activeGateway.mVpnProfile != null) { - launchActiveGateway(); - } + 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); @@ -202,30 +265,48 @@ public final class EIP extends IntentService { intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); intent.putExtra(RECEIVER_TAG, mReceiver); startActivity(intent); - mPending = ACTION_START_EIP; } - - /** - * Disconnects the EIP connection gracefully through the bound service or forcefully - * if there is no bound service. Sends a message to the requesting ResultReceiver. - */ - private void stopEIP() { - if(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"); - } + + /** + * 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"); + } - if (mReceiver != null){ - Bundle resultData = new Bundle(); - resultData.putString(REQUEST_TAG, ACTION_STOP_EIP); - mReceiver.send(Activity.RESULT_OK, resultData); - } + 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; } @@ -257,70 +338,6 @@ public final class EIP extends IntentService { vpl.removeProfile(context, profiles[current_profile]); } } - /** - * Choose a gateway to connect to based on timezone from system locale data - * - * @return The gateway to connect to - */ - private OVPNGateway selectGateway() { - String closestLocation = closestGateway(); - - JSONArray gateways = null; - String chosenHost = null; - try { - gateways = eipDefinition.getJSONArray("gateways"); - for (int i = 0; i < gateways.length(); i++) { - JSONObject gw = gateways.getJSONObject(i); - if ( gw.getString("location").equalsIgnoreCase(closestLocation) || closestLocation.isEmpty()){ - chosenHost = eipDefinition.getJSONObject("locations").getJSONObject(gw.getString("location")).getString("name"); - break; - } - } - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - return new OVPNGateway(chosenHost); - } - - private String closestGateway() { - Calendar cal = Calendar.getInstance(); - int localOffset = cal.get(Calendar.ZONE_OFFSET) / 3600000; - TreeMap<Integer, Set<String>> offsets = new TreeMap<Integer, Set<String>>(); - JSONObject locationsObjects = null; - Iterator<String> locations = null; - try { - locationsObjects = eipDefinition.getJSONObject("locations"); - locations = locationsObjects.keys(); - } catch (JSONException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - - while (locations.hasNext()) { - String locationName = locations.next(); - JSONObject location = null; - try { - location = locationsObjects.getJSONObject(locationName); - - // Distance along the numberline of Prime Meridian centric, assumes UTC-11 through UTC+12 - int dist = Math.abs(localOffset - location.optInt("timezone")); - // Farther than 12 timezones and it's shorter around the "back" - if (dist > 12) - dist = 12 - (dist -12); // Well i'll be. Absolute values make equations do funny things. - - Set<String> set = offsets.get(dist); - if (set == null) set = new HashSet<String>(); - set.add(locationName); - offsets.put(dist, set); - } catch (JSONException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - return offsets.isEmpty() ? "" : offsets.firstEntry().getValue().iterator().next(); - } /** * Walk the list of gateways defined in eip-service.json and parse them into diff --git a/app/src/main/java/se/leap/bitmaskclient/VoidVpnLauncher.java b/app/src/main/java/se/leap/bitmaskclient/VoidVpnLauncher.java new file mode 100644 index 00000000..3b286fbf --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/VoidVpnLauncher.java @@ -0,0 +1,37 @@ +package se.leap.bitmaskclient; + +import android.app.Activity; +import android.content.Intent; +import android.net.VpnService; +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); + setUp(); + } + + public void setUp() { + Intent blocking_intent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application. + if(blocking_intent != null) + startActivityForResult(blocking_intent, VPN_USER_PERMISSION); + else { + onActivityResult(VPN_USER_PERMISSION, RESULT_OK, null); + } + } + + protected void onActivityResult(int requestCode, int resultCode, Intent data){ + 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); + startService(void_vpn_service); + } + } + finish(); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/VoidVpnService.java b/app/src/main/java/se/leap/bitmaskclient/VoidVpnService.java new file mode 100644 index 00000000..b7289c23 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/VoidVpnService.java @@ -0,0 +1,42 @@ +package se.leap.bitmaskclient; + +import android.content.Intent; +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) { + new Thread(new Runnable() { + public void run() { + blockConnections(); + } + }).run(); + } + return 0; + } + + public void blockConnections() { + Builder builder = new Builder(); + builder.setSession("Blocking until running"); + builder.addAddress("10.42.0.8",16); + builder.addRoute("0.0.0.0", 1); + builder.addRoute("128.0.0.0", 1); + builder.addRoute("192.168.1.0", 24); + builder.addDnsServer("10.42.0.1"); + builder.establish(); + android.util.Log.d(TAG, "VoidVpnService set up"); + try { + new java.net.Socket("sdf.org", 80); + Log.d(TAG, "VoidVpnService doesn's stop traffic"); + } catch (Exception e) { + e.printStackTrace(); + } + } +} |