Merge branch 'android-updates'

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.
This commit is contained in:
Tobias Brunner
2016-12-08 17:33:11 +01:00
16 changed files with 175 additions and 44 deletions
@@ -17,29 +17,9 @@
package org.strongswan.android.logic; 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.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.ComponentName; import android.content.ComponentName;
@@ -53,13 +33,39 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.security.KeyChain; import android.security.KeyChain;
import android.security.KeyChainException; import android.security.KeyChainException;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.system.OsConstants; import android.system.OsConstants;
import android.util.Log; 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(); private static final String TAG = CharonVpnService.class.getSimpleName();
public static final String LOG_FILE = "charon.log"; public static final String LOG_FILE = "charon.log";
public static final int VPN_STATE_NOTIFICATION_ID = 1;
private String mLogFile; private String mLogFile;
private VpnProfileDataSource mDataSource; private VpnProfileDataSource mDataSource;
@@ -71,6 +77,7 @@ public class CharonVpnService extends VpnService implements Runnable
private volatile boolean mProfileUpdated; private volatile boolean mProfileUpdated;
private volatile boolean mTerminate; private volatile boolean mTerminate;
private volatile boolean mIsDisconnecting; private volatile boolean mIsDisconnecting;
private volatile boolean mShowNotification;
private VpnStateService mService; private VpnStateService mService;
private final Object mServiceLock = new Object(); private final Object mServiceLock = new Object();
private final ServiceConnection mServiceConnection = new ServiceConnection() { private final ServiceConnection mServiceConnection = new ServiceConnection() {
@@ -91,6 +98,7 @@ public class CharonVpnService extends VpnService implements Runnable
mService = ((VpnStateService.LocalBinder)service).getService(); mService = ((VpnStateService.LocalBinder)service).getService();
} }
/* we are now ready to start the handler thread */ /* we are now ready to start the handler thread */
mService.registerListener(CharonVpnService.this);
mConnectionHandler.start(); mConnectionHandler.start();
} }
}; };
@@ -163,6 +171,7 @@ public class CharonVpnService extends VpnService implements Runnable
} }
if (mService != null) if (mService != null)
{ {
mService.unregisterListener(this);
unbindService(mServiceConnection); unbindService(mServiceConnection);
} }
mDataSource.close(); mDataSource.close();
@@ -220,6 +229,7 @@ public class CharonVpnService extends VpnService implements Runnable
startConnection(mCurrentProfile); startConnection(mCurrentProfile);
mIsDisconnecting = false; mIsDisconnecting = false;
addNotification();
BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling()); BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling());
if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD))) if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
{ {
@@ -268,10 +278,98 @@ public class CharonVpnService extends VpnService implements Runnable
deinitializeCharon(); deinitializeCharon();
Log.i(TAG, "charon stopped"); Log.i(TAG, "charon stopped");
mCurrentProfile = null; 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. * Notify the state service about a new connection attempt.
* Called by the handler thread. * Called by the handler thread.
@@ -444,7 +542,7 @@ public class CharonVpnService extends VpnService implements Runnable
private byte[][] getTrustedCertificates() private byte[][] getTrustedCertificates()
{ {
ArrayList<byte[]> certs = new ArrayList<byte[]>(); ArrayList<byte[]> certs = new ArrayList<byte[]>();
TrustedCertificateManager certman = TrustedCertificateManager.getInstance(); TrustedCertificateManager certman = TrustedCertificateManager.getInstance().load();
try try
{ {
String alias = this.mCurrentCertificateAlias; String alias = this.mCurrentCertificateAlias;
@@ -521,7 +619,7 @@ public class CharonVpnService extends VpnService implements Runnable
* *
* @param builder BuilderAdapter for this connection * @param builder BuilderAdapter for this connection
* @param logfile absolute path to the logfile * @param logfile absolute path to the logfile
* @param boyd enable BYOD features * @param byod enable BYOD features
* @return TRUE if initialization was successful * @return TRUE if initialization was successful
*/ */
public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod); public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod);
@@ -315,7 +315,7 @@ public class VpnStateService extends Service
* *
* May be called from threads other than the main thread. * May be called from threads other than the main thread.
* *
* @param error error state * @param state IMC state
*/ */
public void setImcState(final ImcState state) public void setImcState(final ImcState state)
{ {
@@ -496,14 +496,12 @@ public class VpnProfileDetailActivity extends AppCompatActivity
showCertificateAlert(); showCertificateAlert();
valid = false; valid = false;
} }
Integer mtu = getInteger(mMTU); if (!validateInteger(mMTU, MTU_MIN, MTU_MAX))
if (mtu != null && (mtu < MTU_MIN || mtu > MTU_MAX))
{ {
mMTUWrap.setError(String.format(getString(R.string.alert_text_out_of_range), MTU_MIN, MTU_MAX)); mMTUWrap.setError(String.format(getString(R.string.alert_text_out_of_range), MTU_MIN, MTU_MAX));
valid = false; valid = false;
} }
Integer port = getInteger(mPort); if (!validateInteger(mPort, 1, 65535))
if (port != null && (port < 1 || port > 65535))
{ {
mPortWrap.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535)); mPortWrap.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535));
valid = false; 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 private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback
{ {
@Override @Override
@@ -187,12 +187,11 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
State state = mService.getState(); State state = mService.getState();
ErrorState error = mService.getErrorState(); ErrorState error = mService.getErrorState();
ImcState imcState = mService.getImcState(); ImcState imcState = mService.getImcState();
String name = "", gateway = ""; String name = "";
if (profile != null) if (profile != null)
{ {
name = profile.getName(); name = profile.getName();
gateway = profile.getGateway();
} }
if (reportError(connectionID, name, error, imcState)) if (reportError(connectionID, name, error, imcState))
@@ -6,7 +6,7 @@ include $(CLEAR_VARS)
strongswan_USE_BYOD := true strongswan_USE_BYOD := true
strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ 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 eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls
ifneq ($(strongswan_USE_BYOD),) ifneq ($(strongswan_USE_BYOD),)
@@ -58,7 +58,7 @@ METHOD(attribute_handler_t, handle, bool,
DESTROY_IF(dns); DESTROY_IF(dns);
return FALSE; return FALSE;
} }
DBG1(DBG_IKE, "installing DNS server %H", dns);
builder = charonservice->get_vpnservice_builder(charonservice); builder = charonservice->get_vpnservice_builder(charonservice);
builder->add_dns(builder, dns); builder->add_dns(builder, dns);
dns->destroy(dns); dns->destroy(dns);
@@ -550,6 +550,8 @@ METHOD(listener_t, alert, bool,
private_android_service_t *this, ike_sa_t *ike_sa, alert_t alert, private_android_service_t *this, ike_sa_t *ike_sa, alert_t alert,
va_list args) va_list args)
{ {
bool stay_registered = TRUE;
if (this->ike_sa == ike_sa) if (this->ike_sa == ike_sa)
{ {
switch (alert) switch (alert)
@@ -557,11 +559,13 @@ METHOD(listener_t, alert, bool,
case ALERT_PEER_ADDR_FAILED: case ALERT_PEER_ADDR_FAILED:
charonservice->update_status(charonservice, charonservice->update_status(charonservice,
CHARONSERVICE_LOOKUP_ERROR); CHARONSERVICE_LOOKUP_ERROR);
break; return FALSE;
case ALERT_PEER_AUTH_FAILED: case ALERT_PEER_AUTH_FAILED:
charonservice->update_status(charonservice, charonservice->update_status(charonservice,
CHARONSERVICE_PEER_AUTH_ERROR); CHARONSERVICE_PEER_AUTH_ERROR);
break; return FALSE;
case ALERT_KEEP_ON_CHILD_SA_FAILURE: case ALERT_KEEP_ON_CHILD_SA_FAILURE:
{ {
uint32_t *id = malloc_thing(uint32_t); uint32_t *id = malloc_thing(uint32_t);
@@ -593,6 +597,7 @@ METHOD(listener_t, alert, bool,
(job_t*)callback_job_create_with_prio( (job_t*)callback_job_create_with_prio(
(callback_job_cb_t)terminate, id, free, (callback_job_cb_t)terminate, id, free,
(callback_job_cancel_t)return_false, JOB_PRIO_HIGH)); (callback_job_cancel_t)return_false, JOB_PRIO_HIGH));
stay_registered = FALSE;
} }
else else
{ {
@@ -609,6 +614,7 @@ METHOD(listener_t, alert, bool,
{ {
charonservice->update_status(charonservice, charonservice->update_status(charonservice,
CHARONSERVICE_UNREACHABLE_ERROR); CHARONSERVICE_UNREACHABLE_ERROR);
stay_registered = FALSE;
} }
} }
this->lock->unlock(this->lock); this->lock->unlock(this->lock);
@@ -617,7 +623,7 @@ METHOD(listener_t, alert, bool,
break; break;
} }
} }
return TRUE; return stay_registered;
} }
static void add_auth_cfg_pw(private_android_service_t *this, 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 /* create ESP proposals with and without DH groups, let responder decide
* if PFS is used */ * if PFS is used */
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, 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, 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, child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
"aes256-sha384-ecp521-modp8192")); "aes256-sha384-ecp521-modp8192"));
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
"aes128-aes192-aes256-sha1-sha256-sha384-sha512-" "aes128-aes192-aes256-sha1-sha256-sha384-sha512-"
"ecp256-ecp384-ecp521-" "curve25519-ecp256-ecp384-ecp521-"
"modp2048-modp3072-modp4096-modp1024")); "modp2048-modp3072-modp4096-modp1024"));
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, 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, child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
"aes128-sha256")); "aes128-sha256"));
child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP,
Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

+1 -1
View File
@@ -3,7 +3,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.1.2' classpath 'com.android.tools.build:gradle:2.2.3'
} }
} }
@@ -1,6 +1,6 @@
#Wed Apr 13 11:22:32 CEST 2016 #Tue Sep 20 17:56:35 CEST 2016
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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
+4
View File
@@ -64,12 +64,16 @@ LOCAL_SRC_FILES := $(libstrongswan_la_SOURCES)
LOCAL_SRC_FILES += $(call add_plugin, aes) LOCAL_SRC_FILES += $(call add_plugin, aes)
LOCAL_SRC_FILES += $(call add_plugin, chapoly)
LOCAL_SRC_FILES += $(call add_plugin, curl) LOCAL_SRC_FILES += $(call add_plugin, curl)
ifneq ($(call plugin_enabled, curl),) ifneq ($(call plugin_enabled, curl),)
LOCAL_C_INCLUDES += $(libcurl_PATH) LOCAL_C_INCLUDES += $(libcurl_PATH)
LOCAL_SHARED_LIBRARIES += libcurl LOCAL_SHARED_LIBRARIES += libcurl
endif endif
LOCAL_SRC_FILES += $(call add_plugin, curve25519)
LOCAL_SRC_FILES += $(call add_plugin, des) LOCAL_SRC_FILES += $(call add_plugin, des)
LOCAL_SRC_FILES += $(call add_plugin, fips-prf) LOCAL_SRC_FILES += $(call add_plugin, fips-prf)