Merge branch 'android-updates'
authorTobias Brunner <tobias@strongswan.org>
Thu, 8 Dec 2016 16:33:11 +0000 (17:33 +0100)
committerTobias Brunner <tobias@strongswan.org>
Thu, 8 Dec 2016 16:33:11 +0000 (17:33 +0100)
Adds a permanent notification while connected (or connecting), which
allows running as a foreground service, which in turn should prevent
Android from terminating the service when low on memory.

Also adds support for ChaCha20/Poly1305 AEAD and Curve25519 DH.

16 files changed:
src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java
src/frontends/android/app/src/main/jni/Android.mk
src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_attr.c
src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c
src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification.png [new file with mode: 0644]
src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_warning.png [new file with mode: 0644]
src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification.png [new file with mode: 0644]
src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_warning.png [new file with mode: 0644]
src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification.png [new file with mode: 0644]
src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_warning.png [new file with mode: 0644]
src/frontends/android/build.gradle
src/frontends/android/gradle/wrapper/gradle-wrapper.properties
src/libstrongswan/Android.mk

index a6b9fc5..0048a2d 100644 (file)
 
 package org.strongswan.android.logic;
 
-import java.io.File;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.security.PrivateKey;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-import org.strongswan.android.data.VpnProfile;
-import org.strongswan.android.data.VpnProfileDataSource;
-import org.strongswan.android.data.VpnType.VpnTypeFeature;
-import org.strongswan.android.logic.VpnStateService.ErrorState;
-import org.strongswan.android.logic.VpnStateService.State;
-import org.strongswan.android.logic.imc.ImcState;
-import org.strongswan.android.logic.imc.RemediationInstruction;
-import org.strongswan.android.ui.MainActivity;
-import org.strongswan.android.utils.SettingsWriter;
-
 import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
 import android.content.ComponentName;
@@ -53,13 +33,39 @@ import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.security.KeyChain;
 import android.security.KeyChainException;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.content.ContextCompat;
 import android.system.OsConstants;
 import android.util.Log;
 
-public class CharonVpnService extends VpnService implements Runnable
+import org.strongswan.android.R;
+import org.strongswan.android.data.VpnProfile;
+import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnType.VpnTypeFeature;
+import org.strongswan.android.logic.VpnStateService.ErrorState;
+import org.strongswan.android.logic.VpnStateService.State;
+import org.strongswan.android.logic.imc.ImcState;
+import org.strongswan.android.logic.imc.RemediationInstruction;
+import org.strongswan.android.ui.MainActivity;
+import org.strongswan.android.utils.SettingsWriter;
+
+import java.io.File;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+public class CharonVpnService extends VpnService implements Runnable, VpnStateService.VpnStateListener
 {
        private static final String TAG = CharonVpnService.class.getSimpleName();
        public static final String LOG_FILE = "charon.log";
+       public static final int VPN_STATE_NOTIFICATION_ID = 1;
 
        private String mLogFile;
        private VpnProfileDataSource mDataSource;
@@ -71,6 +77,7 @@ public class CharonVpnService extends VpnService implements Runnable
        private volatile boolean mProfileUpdated;
        private volatile boolean mTerminate;
        private volatile boolean mIsDisconnecting;
+       private volatile boolean mShowNotification;
        private VpnStateService mService;
        private final Object mServiceLock = new Object();
        private final ServiceConnection mServiceConnection = new ServiceConnection() {
@@ -91,6 +98,7 @@ public class CharonVpnService extends VpnService implements Runnable
                                mService = ((VpnStateService.LocalBinder)service).getService();
                        }
                        /* we are now ready to start the handler thread */
+                       mService.registerListener(CharonVpnService.this);
                        mConnectionHandler.start();
                }
        };
@@ -163,6 +171,7 @@ public class CharonVpnService extends VpnService implements Runnable
                }
                if (mService != null)
                {
+                       mService.unregisterListener(this);
                        unbindService(mServiceConnection);
                }
                mDataSource.close();
@@ -220,6 +229,7 @@ public class CharonVpnService extends VpnService implements Runnable
                                                startConnection(mCurrentProfile);
                                                mIsDisconnecting = false;
 
+                                               addNotification();
                                                BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling());
                                                if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
                                                {
@@ -268,11 +278,99 @@ public class CharonVpnService extends VpnService implements Runnable
                                deinitializeCharon();
                                Log.i(TAG, "charon stopped");
                                mCurrentProfile = null;
+                               removeNotification();
                        }
                }
        }
 
        /**
+        * Add a permanent notification while we are connected to avoid the service getting killed by
+        * the system when low on memory.
+        */
+       private void addNotification()
+       {
+               mShowNotification = true;
+               startForeground(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
+       }
+
+       /**
+        * Remove the permanent notification.
+        */
+       private void removeNotification()
+       {
+               mShowNotification = false;
+               stopForeground(true);
+       }
+
+       /**
+        * Build a notification matching the current state
+        */
+       private Notification buildNotification(boolean publicVersion)
+       {
+               VpnProfile profile = mService.getProfile();
+               State state = mService.getState();
+               ErrorState error = mService.getErrorState();
+               String name = "";
+
+               if (profile != null)
+               {
+                       name = profile.getName();
+               }
+               android.support.v4.app.NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
+                               .setSmallIcon(R.drawable.ic_notification)
+                               .setCategory(NotificationCompat.CATEGORY_SERVICE)
+                               .setVisibility(publicVersion ? NotificationCompat.VISIBILITY_PUBLIC
+                                                                                        : NotificationCompat.VISIBILITY_PRIVATE);
+               int s = R.string.state_disabled;
+               if (error != ErrorState.NO_ERROR)
+               {
+                       s = R.string.state_error;
+                       builder.setSmallIcon(R.drawable.ic_notification_warning);
+                       builder.setColor(ContextCompat.getColor(this, R.color.error_text));
+               }
+               else
+               {
+                       switch (state)
+                       {
+                               case CONNECTING:
+                                       s = R.string.state_connecting;
+                                       builder.setSmallIcon(R.drawable.ic_notification_warning);
+                                       builder.setColor(ContextCompat.getColor(this, R.color.warning_text));
+                                       break;
+                               case CONNECTED:
+                                       s = R.string.state_connected;
+                                       builder.setColor(ContextCompat.getColor(this, R.color.success_text));
+                                       builder.setUsesChronometer(true);
+                                       break;
+                               case DISCONNECTING:
+                                       s = R.string.state_disconnecting;
+                                       break;
+                       }
+               }
+               builder.setContentTitle(getString(s));
+               if (!publicVersion)
+               {
+                       builder.setContentText(name);
+                       builder.setPublicVersion(buildNotification(true));
+               }
+
+               Intent intent = new Intent(getApplicationContext(), MainActivity.class);
+               PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
+                                                                                                                 PendingIntent.FLAG_UPDATE_CURRENT);
+               builder.setContentIntent(pending);
+               return builder.build();
+       }
+
+       @Override
+       public void stateChanged() {
+               if (mShowNotification)
+               {
+                       NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+                       manager.notify(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
+               }
+       }
+
+       /**
         * Notify the state service about a new connection attempt.
         * Called by the handler thread.
         *
@@ -444,7 +542,7 @@ public class CharonVpnService extends VpnService implements Runnable
        private byte[][] getTrustedCertificates()
        {
                ArrayList<byte[]> certs = new ArrayList<byte[]>();
-               TrustedCertificateManager certman = TrustedCertificateManager.getInstance();
+               TrustedCertificateManager certman = TrustedCertificateManager.getInstance().load();
                try
                {
                        String alias = this.mCurrentCertificateAlias;
@@ -521,7 +619,7 @@ public class CharonVpnService extends VpnService implements Runnable
         *
         * @param builder BuilderAdapter for this connection
         * @param logfile absolute path to the logfile
-        * @param boyd enable BYOD features
+        * @param byod enable BYOD features
         * @return TRUE if initialization was successful
         */
        public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod);
index 7b40e94..e35277d 100644 (file)
@@ -315,7 +315,7 @@ public class VpnStateService extends Service
         *
         * May be called from threads other than the main thread.
         *
-        * @param error error state
+        * @param state IMC state
         */
        public void setImcState(final ImcState state)
        {
index dbbfaef..30fb101 100644 (file)
@@ -496,14 +496,12 @@ public class VpnProfileDetailActivity extends AppCompatActivity
                        showCertificateAlert();
                        valid = false;
                }
-               Integer mtu = getInteger(mMTU);
-               if (mtu != null && (mtu < MTU_MIN || mtu > MTU_MAX))
+               if (!validateInteger(mMTU, MTU_MIN, MTU_MAX))
                {
                        mMTUWrap.setError(String.format(getString(R.string.alert_text_out_of_range), MTU_MIN, MTU_MAX));
                        valid = false;
                }
-               Integer port = getInteger(mPort);
-               if (port != null && (port < 1 || port > 65535))
+               if (!validateInteger(mPort, 1, 65535))
                {
                        mPortWrap.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535));
                        valid = false;
@@ -633,6 +631,31 @@ public class VpnProfileDetailActivity extends AppCompatActivity
                }
        }
 
+       /**
+        * Check that the value in the given text box is a valid integer in the given range
+        *
+        * @param view text box (numeric entry assumed)
+        * @param min minimum value (inclusive)
+        * @param max maximum value (inclusive)
+        */
+       private boolean validateInteger(EditText view, Integer min, Integer max)
+       {
+               String value = view.getText().toString().trim();
+               try
+               {
+                       if (value.isEmpty())
+                       {
+                               return true;
+                       }
+                       Integer val = Integer.valueOf(value);
+                       return min <= val && val <= max;
+               }
+               catch (NumberFormatException e)
+               {
+                       return false;
+               }
+       }
+
        private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback
        {
                @Override
index 0b093d7..1ea0151 100644 (file)
@@ -187,12 +187,11 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
                State state = mService.getState();
                ErrorState error = mService.getErrorState();
                ImcState imcState = mService.getImcState();
-               String name = "", gateway = "";
+               String name = "";
 
                if (profile != null)
                {
                        name = profile.getName();
-                       gateway = profile.getGateway();
                }
 
                if (reportError(connectionID, name, error, imcState))
index 849bdec..51a61b8 100644 (file)
@@ -6,7 +6,7 @@ include $(CLEAR_VARS)
 strongswan_USE_BYOD := true
 
 strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \
-       pkcs1 pkcs8 pem xcbc hmac socket-default \
+       chapoly curve25519 pkcs1 pkcs8 pem xcbc hmac socket-default \
        eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls
 
 ifneq ($(strongswan_USE_BYOD),)
index 8015b7c..47933d1 100644 (file)
@@ -58,7 +58,7 @@ METHOD(attribute_handler_t, handle, bool,
                DESTROY_IF(dns);
                return FALSE;
        }
-
+       DBG1(DBG_IKE, "installing DNS server %H", dns);
        builder = charonservice->get_vpnservice_builder(charonservice);
        builder->add_dns(builder, dns);
        dns->destroy(dns);
index 2532402..33585df 100644 (file)
@@ -550,6 +550,8 @@ METHOD(listener_t, alert, bool,
        private_android_service_t *this, ike_sa_t *ike_sa, alert_t alert,
        va_list args)
 {
+       bool stay_registered = TRUE;
+
        if (this->ike_sa == ike_sa)
        {
                switch (alert)
@@ -557,11 +559,13 @@ METHOD(listener_t, alert, bool,
                        case ALERT_PEER_ADDR_FAILED:
                                charonservice->update_status(charonservice,
                                                                                         CHARONSERVICE_LOOKUP_ERROR);
-                               break;
+                               return FALSE;
+
                        case ALERT_PEER_AUTH_FAILED:
                                charonservice->update_status(charonservice,
                                                                                         CHARONSERVICE_PEER_AUTH_ERROR);
-                               break;
+                               return FALSE;
+
                        case ALERT_KEEP_ON_CHILD_SA_FAILURE:
                        {
                                uint32_t *id = malloc_thing(uint32_t);
@@ -593,6 +597,7 @@ METHOD(listener_t, alert, bool,
                                                (job_t*)callback_job_create_with_prio(
                                                        (callback_job_cb_t)terminate, id, free,
                                                        (callback_job_cancel_t)return_false, JOB_PRIO_HIGH));
+                                       stay_registered = FALSE;
                                }
                                else
                                {
@@ -609,6 +614,7 @@ METHOD(listener_t, alert, bool,
                                        {
                                                charonservice->update_status(charonservice,
                                                                                        CHARONSERVICE_UNREACHABLE_ERROR);
+                                               stay_registered = FALSE;
                                        }
                                }
                                this->lock->unlock(this->lock);
@@ -617,7 +623,7 @@ METHOD(listener_t, alert, bool,
                                break;
                }
        }
-       return TRUE;
+       return stay_registered;
 }
 
 static void add_auth_cfg_pw(private_android_service_t *this,
@@ -789,17 +795,18 @@ static job_requeue_t initiate(private_android_service_t *this)
        /* create ESP proposals with and without DH groups, let responder decide
         * if PFS is used */
        child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
-                                                       "aes128gcm16-aes256gcm16-ecp256"));
+                                                       "aes128gcm16-aes256gcm16-chacha20poly1305-"
+                                                       "curve25519-ecp256-modp3072"));
        child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
-                                                       "aes128-sha256-ecp256-modp3072"));
+                                                       "aes128-sha256-curve25519-ecp256-modp3072"));
        child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
                                                        "aes256-sha384-ecp521-modp8192"));
        child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
                                                        "aes128-aes192-aes256-sha1-sha256-sha384-sha512-"
-                                                       "ecp256-ecp384-ecp521-"
+                                                       "curve25519-ecp256-ecp384-ecp521-"
                                                        "modp2048-modp3072-modp4096-modp1024"));
        child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
-                                                       "aes128gcm16-aes256gcm16"));
+                                                       "aes128gcm16-aes256gcm16-chacha20poly1305"));
        child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
                                                        "aes128-sha256"));
        child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
diff --git a/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification.png b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification.png
new file mode 100644 (file)
index 0000000..d723ee6
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_warning.png b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_warning.png
new file mode 100644 (file)
index 0000000..05198c8
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_warning.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification.png b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification.png
new file mode 100644 (file)
index 0000000..fa5d642
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_warning.png b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_warning.png
new file mode 100644 (file)
index 0000000..f6cd212
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_warning.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification.png b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification.png
new file mode 100644 (file)
index 0000000..9961c0a
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification.png differ
diff --git a/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_warning.png b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_warning.png
new file mode 100644 (file)
index 0000000..1b5be81
Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_warning.png differ
index 33b908c..3d5ba79 100644 (file)
@@ -3,7 +3,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.1.2'
+        classpath 'com.android.tools.build:gradle:2.2.3'
     }
 }
 
index 3389226..a6bd91c 100644 (file)
@@ -1,6 +1,6 @@
-#Wed Apr 13 11:22:32 CEST 2016
+#Tue Sep 20 17:56:35 CEST 2016
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
index 0e8f7f3..b594dab 100644 (file)
@@ -64,12 +64,16 @@ LOCAL_SRC_FILES := $(libstrongswan_la_SOURCES)
 
 LOCAL_SRC_FILES += $(call add_plugin, aes)
 
+LOCAL_SRC_FILES += $(call add_plugin, chapoly)
+
 LOCAL_SRC_FILES += $(call add_plugin, curl)
 ifneq ($(call plugin_enabled, curl),)
 LOCAL_C_INCLUDES += $(libcurl_PATH)
 LOCAL_SHARED_LIBRARIES += libcurl
 endif
 
+LOCAL_SRC_FILES += $(call add_plugin, curve25519)
+
 LOCAL_SRC_FILES += $(call add_plugin, des)
 
 LOCAL_SRC_FILES += $(call add_plugin, fips-prf)