aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/frontends/android/jni/Android.mk2
-rw-r--r--src/frontends/android/jni/libandroidbridge/android_jni.h13
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_creds.c85
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_creds.h7
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_service.c72
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_service.h8
-rw-r--r--src/frontends/android/jni/libandroidbridge/charonservice.c88
-rw-r--r--src/frontends/android/jni/libandroidbridge/charonservice.h11
-rw-r--r--src/frontends/android/res/layout/certificate_selector.xml39
-rw-r--r--src/frontends/android/res/layout/profile_detail_view.xml92
-rw-r--r--src/frontends/android/res/layout/profile_list_item.xml10
-rw-r--r--src/frontends/android/res/values-de/arrays.xml22
-rw-r--r--src/frontends/android/res/values-de/strings.xml5
-rw-r--r--src/frontends/android/res/values-pl/arrays.xml22
-rw-r--r--src/frontends/android/res/values-pl/strings.xml7
-rw-r--r--src/frontends/android/res/values/arrays.xml22
-rw-r--r--src/frontends/android/res/values/strings.xml5
-rw-r--r--src/frontends/android/src/org/strongswan/android/data/VpnProfile.java27
-rw-r--r--src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java58
-rw-r--r--src/frontends/android/src/org/strongswan/android/data/VpnType.java88
-rw-r--r--src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java45
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/MainActivity.java118
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java242
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java20
-rw-r--r--src/libstrongswan/Android.mk2
25 files changed, 929 insertions, 181 deletions
diff --git a/src/frontends/android/jni/Android.mk b/src/frontends/android/jni/Android.mk
index a603a733e..326565f2b 100644
--- a/src/frontends/android/jni/Android.mk
+++ b/src/frontends/android/jni/Android.mk
@@ -2,7 +2,7 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \
- pkcs1 pem xcbc hmac socket-default \
+ pkcs1 pkcs8 pem xcbc hmac socket-default \
eap-identity eap-mschapv2 eap-md5 eap-gtc
strongswan_PLUGINS := $(strongswan_CHARON_PLUGINS)
diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h
index 774d37d7e..bafd6b72e 100644
--- a/src/frontends/android/jni/libandroidbridge/android_jni.h
+++ b/src/frontends/android/jni/libandroidbridge/android_jni.h
@@ -90,13 +90,16 @@ static inline bool androidjni_exception_occurred(JNIEnv *env)
*/
static inline char *androidjni_convert_jstring(JNIEnv *env, jstring jstr)
{
- char *str;
+ char *str = NULL;
jsize len;
- len = (*env)->GetStringUTFLength(env, jstr);
- str = malloc(len + 1);
- (*env)->GetStringUTFRegion(env, jstr, 0, len, str);
- str[len] = '\0';
+ if (jstr)
+ {
+ len = (*env)->GetStringUTFLength(env, jstr);
+ str = malloc(len + 1);
+ (*env)->GetStringUTFRegion(env, jstr, 0, len, str);
+ str[len] = '\0';
+ }
return str;
}
diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.c b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c
index 27023d721..931f22316 100644
--- a/src/frontends/android/jni/libandroidbridge/backend/android_creds.c
+++ b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c
@@ -50,6 +50,15 @@ struct private_android_creds_t {
};
/**
+ * Free allocated DER encoding
+ */
+static void free_encoding(chunk_t *chunk)
+{
+ chunk_free(chunk);
+ free(chunk);
+}
+
+/**
* Load trusted certificates via charonservice (JNI).
*/
static void load_trusted_certificates(private_android_creds_t *this)
@@ -71,8 +80,7 @@ static void load_trusted_certificates(private_android_creds_t *this)
cert->get_subject(cert));
this->creds->add_cert(this->creds, TRUE, cert);
}
- chunk_free(current);
- free(current);
+ free_encoding(current);
}
certs->destroy(certs);
}
@@ -130,6 +138,76 @@ METHOD(credential_set_t, create_shared_enumerator, enumerator_t*,
type, me, other);
}
+METHOD(android_creds_t, load_user_certificate, certificate_t*,
+ private_android_creds_t *this)
+{
+ linked_list_t *encodings;
+ certificate_t *cert = NULL, *ca_cert;
+ private_key_t *key = NULL;
+ chunk_t *current;
+
+ encodings = charonservice->get_user_certificate(charonservice);
+ if (!encodings)
+ {
+ return NULL;
+ }
+
+ while (encodings->remove_first(encodings, (void**)&current) == SUCCESS)
+ {
+ if (!key)
+ { /* the first element is the private key, we assume RSA */
+ key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA,
+ BUILD_BLOB_ASN1_DER, *current, BUILD_END);
+ if (key)
+ {
+ this->creds->add_key(this->creds, key);
+ free_encoding(current);
+ continue;
+ }
+ goto failed;
+ }
+ if (!cert)
+ { /* the next element is the user certificate */
+ cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
+ BUILD_BLOB_ASN1_DER, *current, BUILD_END);
+ if (cert)
+ {
+ DBG1(DBG_CFG, "loaded user certificate '%Y' and private key",
+ cert->get_subject(cert));
+ cert = this->creds->add_cert_ref(this->creds, TRUE, cert);
+ free_encoding(current);
+ continue;
+ }
+ goto failed;
+ }
+ /* the rest are CA certificates, we ignore failures */
+ ca_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
+ BUILD_BLOB_ASN1_DER, *current, BUILD_END);
+ if (ca_cert)
+ {
+ DBG1(DBG_CFG, "loaded CA certificate '%Y'",
+ ca_cert->get_subject(ca_cert));
+ this->creds->add_cert(this->creds, TRUE, ca_cert);
+ }
+ free_encoding(current);
+ }
+ encodings->destroy(encodings);
+ return cert;
+
+failed:
+ DBG1(DBG_CFG, "failed to load user certificate and private key");
+ free_encoding(current);
+ encodings->destroy_function(encodings, (void*)free_encoding);
+ return NULL;
+}
+
+METHOD(credential_set_t, create_private_enumerator, enumerator_t*,
+ private_android_creds_t *this, key_type_t type, identification_t *id)
+{
+ return this->creds->set.create_private_enumerator(&this->creds->set,
+ type, id);
+}
+
METHOD(android_creds_t, clear, void,
private_android_creds_t *this)
{
@@ -160,11 +238,12 @@ android_creds_t *android_creds_create()
.set = {
.create_cert_enumerator = _create_cert_enumerator,
.create_shared_enumerator = _create_shared_enumerator,
- .create_private_enumerator = (void*)return_null,
+ .create_private_enumerator = _create_private_enumerator,
.create_cdp_enumerator = (void*)return_null,
.cache_cert = (void*)nop,
},
.add_username_password = _add_username_password,
+ .load_user_certificate = _load_user_certificate,
.clear = _clear,
.destroy = _destroy,
},
diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.h b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h
index 33de838c1..a3ecddde4 100644
--- a/src/frontends/android/jni/libandroidbridge/backend/android_creds.h
+++ b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h
@@ -47,6 +47,13 @@ struct android_creds_t {
char *password);
/**
+ * Load the user certificate and private key
+ *
+ * @preturn loaded client certificate, NULL on failure
+ */
+ certificate_t *(*load_user_certificate)(android_creds_t *this);
+
+ /**
* Clear the cached certificates and stored credentials.
*/
void (*clear)(android_creds_t *this);
diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c
index d1769a99a..f62aea0e8 100644
--- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c
+++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c
@@ -44,11 +44,21 @@ struct private_android_service_t {
android_service_t public;
/**
+ * credential set
+ */
+ android_creds_t *creds;
+
+ /**
* current IKE_SA
*/
ike_sa_t *ike_sa;
/**
+ * the type of VPN
+ */
+ char *type;
+
+ /**
* local ipv4 address
*/
char *local_address;
@@ -64,6 +74,11 @@ struct private_android_service_t {
char *username;
/**
+ * password
+ */
+ char *password;
+
+ /**
* lock to safely access the TUN device fd
*/
rwlock_t *lock;
@@ -445,11 +460,42 @@ static job_requeue_t initiate(private_android_service_t *this)
FALSE, NULL, NULL); /* mediation */
peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("0.0.0.0", 0));
- auth = auth_cfg_create();
- auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP);
- user = identification_create_from_string(this->username);
- auth->add(auth, AUTH_RULE_IDENTITY, user);
- peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
+ /* local auth config */
+ if (streq("ikev2-eap", this->type))
+ {
+ auth = auth_cfg_create();
+ auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP);
+ user = identification_create_from_string(this->username);
+ auth->add(auth, AUTH_RULE_IDENTITY, user);
+
+ this->creds->add_username_password(this->creds, this->username,
+ this->password);
+ memwipe(this->password, strlen(this->password));
+ peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
+ }
+ else if (streq("ikev2-cert", this->type))
+ {
+ certificate_t *cert;
+ identification_t *id;
+
+ cert = this->creds->load_user_certificate(this->creds);
+ if (!cert)
+ {
+ peer_cfg->destroy(peer_cfg);
+ charonservice->update_status(charonservice,
+ CHARONSERVICE_GENERIC_ERROR);
+ return JOB_REQUEUE_NONE;
+
+ }
+ auth = auth_cfg_create();
+ auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
+ auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert);
+ id = cert->get_subject(cert);
+ auth->add(auth, AUTH_RULE_IDENTITY, id->clone(id));
+ peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
+ }
+
+ /* remote auth config */
auth = auth_cfg_create();
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
gateway = identification_create_from_string(this->gateway);
@@ -506,17 +552,24 @@ METHOD(android_service_t, destroy, void,
/* make sure the tun device is actually closed */
close_tun_device(this);
this->lock->destroy(this->lock);
+ free(this->type);
free(this->local_address);
- free(this->username);
free(this->gateway);
+ free(this->username);
+ if (this->password)
+ {
+ memwipe(this->password, strlen(this->password));
+ free(this->password);
+ }
free(this);
}
/**
* See header
*/
-android_service_t *android_service_create(char *local_address, char *gateway,
- char *username)
+android_service_t *android_service_create(android_creds_t *creds, char *type,
+ char *local_address, char *gateway,
+ char *username, char *password)
{
private_android_service_t *this;
@@ -534,7 +587,10 @@ android_service_t *android_service_create(char *local_address, char *gateway,
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
.local_address = local_address,
.username = username,
+ .password = password,
.gateway = gateway,
+ .creds = creds,
+ .type = type,
.tunfd = -1,
);
diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.h b/src/frontends/android/jni/libandroidbridge/backend/android_service.h
index a7bd8b059..52c3dc5c8 100644
--- a/src/frontends/android/jni/libandroidbridge/backend/android_service.h
+++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.h
@@ -51,11 +51,15 @@ struct android_service_t {
* Create an Android service instance. Queues a job that starts initiation of a
* new IKE SA.
*
+ * @param creds Android specific credential set
+ * @param type VPN type (see VpnType.java)
* @param local_address local ip address
* @param gateway gateway address
* @param username user name (local identity)
+ * @param password password (if any)
*/
-android_service_t *android_service_create(char *local_address, char *gateway,
- char *username);
+android_service_t *android_service_create(android_creds_t *creds, char *type,
+ char *local_address, char *gateway,
+ char *username, char *password);
#endif /** ANDROID_SERVICE_H_ @}*/
diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c
index fab99ac10..59ec62fc7 100644
--- a/src/frontends/android/jni/libandroidbridge/charonservice.c
+++ b/src/frontends/android/jni/libandroidbridge/charonservice.c
@@ -199,6 +199,33 @@ failed:
return FALSE;
}
+/**
+ * Converts the given Java array of byte arrays (byte[][]) to a linked list
+ * of chunk_t objects.
+ */
+static linked_list_t *convert_array_of_byte_arrays(JNIEnv *env,
+ jobjectArray jarray)
+{
+ linked_list_t *list;
+ jsize i;
+
+ list = linked_list_create();
+ for (i = 0; i < (*env)->GetArrayLength(env, jarray); ++i)
+ {
+ chunk_t *chunk;
+ jbyteArray jbytearray;
+
+ chunk = malloc_thing(chunk_t);
+ list->insert_last(list, chunk);
+
+ jbytearray = (*env)->GetObjectArrayElement(env, jarray, i);
+ *chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray));
+ (*env)->GetByteArrayRegion(env, jbytearray, 0, chunk->len, chunk->ptr);
+ (*env)->DeleteLocalRef(env, jbytearray);
+ }
+ return list;
+}
+
METHOD(charonservice_t, get_trusted_certificates, linked_list_t*,
private_charonservice_t *this)
{
@@ -206,7 +233,6 @@ METHOD(charonservice_t, get_trusted_certificates, linked_list_t*,
jmethodID method_id;
jobjectArray jcerts;
linked_list_t *list;
- jsize i;
androidjni_attach_thread(&env);
@@ -222,21 +248,39 @@ METHOD(charonservice_t, get_trusted_certificates, linked_list_t*,
{
goto failed;
}
- list = linked_list_create();
- for (i = 0; i < (*env)->GetArrayLength(env, jcerts); ++i)
- {
- chunk_t *ca_cert;
- jbyteArray jcert;
+ list = convert_array_of_byte_arrays(env, jcerts);
+ androidjni_detach_thread();
+ return list;
- ca_cert = malloc_thing(chunk_t);
- list->insert_last(list, ca_cert);
+failed:
+ androidjni_exception_occurred(env);
+ androidjni_detach_thread();
+ return NULL;
+}
+
+METHOD(charonservice_t, get_user_certificate, linked_list_t*,
+ private_charonservice_t *this)
+{
+ JNIEnv *env;
+ jmethodID method_id;
+ jobjectArray jencodings;
+ linked_list_t *list;
+
+ androidjni_attach_thread(&env);
- jcert = (*env)->GetObjectArrayElement(env, jcerts, i);
- *ca_cert = chunk_alloc((*env)->GetArrayLength(env, jcert));
- (*env)->GetByteArrayRegion(env, jcert, 0, ca_cert->len, ca_cert->ptr);
- (*env)->DeleteLocalRef(env, jcert);
+ method_id = (*env)->GetMethodID(env,
+ android_charonvpnservice_class,
+ "getUserCertificate", "()[[B");
+ if (!method_id)
+ {
+ goto failed;
}
- (*env)->DeleteLocalRef(env, jcerts);
+ jencodings = (*env)->CallObjectMethod(env, this->vpn_service, method_id, NULL);
+ if (!jencodings)
+ {
+ goto failed;
+ }
+ list = convert_array_of_byte_arrays(env, jencodings);
androidjni_detach_thread();
return list;
@@ -260,17 +304,15 @@ METHOD(charonservice_t, get_vpnservice_builder, vpnservice_builder_t*,
* @param username username (gets owned)
* @param password password (gets owned)
*/
-static void initiate(char *local, char *gateway, char *username, char *password)
+static void initiate(char *type, char *local, char *gateway,
+ char *username, char *password)
{
private_charonservice_t *this = (private_charonservice_t*)charonservice;
this->creds->clear(this->creds);
- this->creds->add_username_password(this->creds, username, password);
- memwipe(password, strlen(password));
- free(password);
-
DESTROY_IF(this->service);
- this->service = android_service_create(local, gateway, username);
+ this->service = android_service_create(this->creds, type, local, gateway,
+ username, password);
}
/**
@@ -321,6 +363,7 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder)
.update_status = _update_status,
.bypass_socket = _bypass_socket,
.get_trusted_certificates = _get_trusted_certificates,
+ .get_user_certificate = _get_user_certificate,
.get_vpnservice_builder = _get_vpnservice_builder,
},
.attr = android_attr_create(),
@@ -477,15 +520,16 @@ JNI_METHOD(CharonVpnService, deinitializeCharon, void)
* Initiate SA
*/
JNI_METHOD(CharonVpnService, initiate, void,
- jstring jlocal_address, jstring jgateway, jstring jusername,
+ jstring jtype, jstring jlocal_address, jstring jgateway, jstring jusername,
jstring jpassword)
{
- char *local_address, *gateway, *username, *password;
+ char *type, *local_address, *gateway, *username, *password;
+ type = androidjni_convert_jstring(env, jtype);
local_address = androidjni_convert_jstring(env, jlocal_address);
gateway = androidjni_convert_jstring(env, jgateway);
username = androidjni_convert_jstring(env, jusername);
password = androidjni_convert_jstring(env, jpassword);
- initiate(local_address, gateway, username, password);
+ initiate(type, local_address, gateway, username, password);
}
diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h
index 706eaa220..507010bad 100644
--- a/src/frontends/android/jni/libandroidbridge/charonservice.h
+++ b/src/frontends/android/jni/libandroidbridge/charonservice.h
@@ -86,6 +86,17 @@ struct charonservice_t {
linked_list_t *(*get_trusted_certificates)(charonservice_t *this);
/**
+ * Get the configured user certificate chain and private key via JNI
+ *
+ * The first item in the returned list is the private key, followed by the
+ * user certificate and any remaining elements of the certificate chain.
+ *
+ * @return list of DER encoded objects (as chunk_t*),
+ * NULL on failure
+ */
+ linked_list_t *(*get_user_certificate)(charonservice_t *this);
+
+ /**
* Get the current vpnservice_builder_t object
*
* @return VpnService.Builder instance
diff --git a/src/frontends/android/res/layout/certificate_selector.xml b/src/frontends/android/res/layout/certificate_selector.xml
new file mode 100644
index 000000000..c8c25811b
--- /dev/null
+++ b/src/frontends/android/res/layout/certificate_selector.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 Tobias Brunner
+ Hochschule fuer Technik Rapperswil
+
+ 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 2 of the License, or (at your
+ option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+
+ 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.
+-->
+<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:background="?android:attr/selectableItemBackground"
+ android:mode="twoLine"
+ android:padding="10dp" >
+
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@android:id/text2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/text1"
+ android:layout_alignLeft="@android:id/text1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary" />
+
+</TwoLineListItem>
diff --git a/src/frontends/android/res/layout/profile_detail_view.xml b/src/frontends/android/res/layout/profile_detail_view.xml
index d9ccca546..39c94348b 100644
--- a/src/frontends/android/res/layout/profile_detail_view.xml
+++ b/src/frontends/android/res/layout/profile_detail_view.xml
@@ -56,28 +56,67 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
- android:text="@string/profile_username_label" />
+ android:text="@string/profile_vpn_type_label" />
- <EditText
- android:id="@+id/username"
+ <Spinner
+ android:id="@+id/vpn_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:singleLine="true"
- android:inputType="textNoSuggestions" />
+ android:spinnerMode="dropdown"
+ android:entries="@array/vpn_types" />
- <TextView
+ <LinearLayout
+ android:id="@+id/username_password_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:text="@string/profile_password_label" />
+ android:orientation="vertical" >
- <EditText
- android:id="@+id/password"
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="@string/profile_username_label" />
+
+ <EditText
+ android:id="@+id/username"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:inputType="textNoSuggestions" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="@string/profile_password_label" />
+
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:inputType="textPassword|textNoSuggestions"
+ android:hint="@string/profile_password_hint" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/user_certificate_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:singleLine="true"
- android:inputType="textPassword|textNoSuggestions"
- android:hint="@string/profile_password_hint" />
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="@string/profile_user_certificate_label" />
+
+ <include
+ android:id="@+id/select_user_certificate"
+ layout="@layout/certificate_selector" />
+
+ </LinearLayout>
<TextView
android:layout_width="match_parent"
@@ -91,32 +130,9 @@
android:layout_height="wrap_content"
android:text="@string/profile_ca_auto_label" />
- <RelativeLayout
+ <include
android:id="@+id/select_certificate"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:background="?android:attr/selectableItemBackground"
- android:padding="10dp" >
-
- <TextView
- android:id="@+id/select_certificate_title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/profile_ca_select_certificate_label" />
-
- <TextView
- android:id="@+id/select_certificate_subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/select_certificate_title"
- android:layout_alignLeft="@id/select_certificate_title"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorSecondary"
- android:text="@string/profile_ca_select_certificate" />
-
- </RelativeLayout>
+ layout="@layout/certificate_selector" />
</LinearLayout>
diff --git a/src/frontends/android/res/layout/profile_list_item.xml b/src/frontends/android/res/layout/profile_list_item.xml
index f55c8357a..93df7b649 100644
--- a/src/frontends/android/res/layout/profile_list_item.xml
+++ b/src/frontends/android/res/layout/profile_list_item.xml
@@ -46,4 +46,14 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_marginLeft="15dp" />
+ <TextView
+ android:id="@+id/profile_item_certificate"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="?android:textColorSecondary"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_marginLeft="15dp" />
+
</LinearLayout>
diff --git a/src/frontends/android/res/values-de/arrays.xml b/src/frontends/android/res/values-de/arrays.xml
new file mode 100644
index 000000000..efa4bcb03
--- /dev/null
+++ b/src/frontends/android/res/values-de/arrays.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 Tobias Brunner
+ Hochschule fuer Technik Rapperswil
+
+ 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 2 of the License, or (at your
+ option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+
+ 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.
+-->
+<resources>
+ <!-- the order here must match the enum entries in VpnType.java -->
+ <string-array name="vpn_types">
+ <item>IKEv2 EAP (Benutzername/Passwort)</item>
+ <item>IKEv2 Zertifikat</item>
+ </string-array>
+</resources> \ No newline at end of file
diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml
index 9f3f637a8..a04da7208 100644
--- a/src/frontends/android/res/values-de/strings.xml
+++ b/src/frontends/android/res/values-de/strings.xml
@@ -25,6 +25,7 @@
<string name="search">Suchen</string>
<string name="vpn_not_supported_title">VPN nicht unterstützt</string>
<string name="vpn_not_supported">Ihr Gerät unterstützt keine VPN Anwendungen.\nBitte kontaktieren Sie den Hersteller.</string>
+ <string name="loading">Laden&#8230;</string>
<!-- Log view -->
<string name="log_title">Log</string>
@@ -49,9 +50,13 @@
<string name="profile_name_label">Profilname:</string>
<string name="profile_name_hint">(Gateway-Adresse verwenden)</string>
<string name="profile_gateway_label">Gateway:</string>
+ <string name="profile_vpn_type_label">Typ:</string>
<string name="profile_username_label">Benutzername:</string>
<string name="profile_password_label">Passwort:</string>
<string name="profile_password_hint">(anfordern wenn benötigt)</string>
+ <string name="profile_user_certificate_label">Benutzer-Zertifikat:</string>
+ <string name="profile_user_select_certificate_label">Benutzer-Zertifikat auswählen</string>
+ <string name="profile_user_select_certificate">Wählen Sie ein bestimmtes Benutzer-Zertifikat</string>
<string name="profile_ca_label">CA-Zertifikat:</string>
<string name="profile_ca_auto_label">Automatisch wählen</string>
<string name="profile_ca_select_certificate_label">CA-Zertifikat auswählen</string>
diff --git a/src/frontends/android/res/values-pl/arrays.xml b/src/frontends/android/res/values-pl/arrays.xml
new file mode 100644
index 000000000..3e1af5f82
--- /dev/null
+++ b/src/frontends/android/res/values-pl/arrays.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 Tobias Brunner
+ Hochschule fuer Technik Rapperswil
+
+ 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 2 of the License, or (at your
+ option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+
+ 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.
+-->
+<resources>
+ <!-- the order here must match the enum entries in VpnType.java -->
+ <string-array name="vpn_types">
+ <item>IKEv2 EAP (użytkownik/hasło)</item>
+ <item>IKEv2 certyfikat</item>
+ </string-array>
+</resources> \ No newline at end of file
diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml
index e7d4670d9..54f4259ae 100644
--- a/src/frontends/android/res/values-pl/strings.xml
+++ b/src/frontends/android/res/values-pl/strings.xml
@@ -27,6 +27,7 @@
<string name="search">Szukaj</string>
<string name="vpn_not_supported_title">Nie obsługiwany VPN</string>
<string name="vpn_not_supported">Urządzenie nie obsługuje aplikacji VPN.\nProszę skontaktować się z producentem.</string>
+ <string name="loading">Wczytywanie&#8230;</string>
<!-- Log view -->
<string name="log_title">Log</string>
@@ -51,9 +52,13 @@
<string name="profile_name_label">Nazwa profilu:</string>
<string name="profile_name_hint">(użyj adresu bramki)</string>
<string name="profile_gateway_label">Bramka:</string>
+ <string name="profile_vpn_type_label">Typ:</string>
<string name="profile_username_label">Użytkownik:</string>
<string name="profile_password_label">Hasło:</string>
- <string name="profile_password_hint">(w razie potrzebz zapromptuj)</string>
+ <string name="profile_password_hint">(w razie potrzeby zapromptuj)</string>
+ <string name="profile_user_certificate_label">Certyfikat użytkownika:</string>
+ <string name="profile_user_select_certificate_label">Wybierz certyfikat użytkownika</string>
+ <string name="profile_user_select_certificate">>Wybierz określony certyfikat użytkownika</string>
<string name="profile_ca_label">Certyfikat CA:</string>
<string name="profile_ca_auto_label">Wybierz automatycznie</string>
<string name="profile_ca_select_certificate_label">Wybierz certyfikat CA</string>
diff --git a/src/frontends/android/res/values/arrays.xml b/src/frontends/android/res/values/arrays.xml
new file mode 100644
index 000000000..21576f22c
--- /dev/null
+++ b/src/frontends/android/res/values/arrays.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 Tobias Brunner
+ Hochschule fuer Technik Rapperswil
+
+ 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 2 of the License, or (at your
+ option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+
+ 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.
+-->
+<resources>
+ <!-- the order here must match the enum entries in VpnType.java -->
+ <string-array name="vpn_types">
+ <item>IKEv2 EAP (Username/Password)</item>
+ <item>IKEv2 Certificate</item>
+ </string-array>
+</resources> \ No newline at end of file
diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml
index bc7fa4a1d..3e4b746fd 100644
--- a/src/frontends/android/res/values/strings.xml
+++ b/src/frontends/android/res/values/strings.xml
@@ -25,6 +25,7 @@
<string name="search">Search</string>
<string name="vpn_not_supported_title">VPN not supported</string>
<string name="vpn_not_supported">Your device does not support VPN applications.\nPlease contact the manufacturer.</string>
+ <string name="loading">Loading&#8230;</string>
<!-- Log view -->
<string name="log_title">Log</string>
@@ -49,9 +50,13 @@
<string name="profile_name_label">Profile Name:</string>
<string name="profile_name_hint">(use gateway address)</string>
<string name="profile_gateway_label">Gateway:</string>
+ <string name="profile_vpn_type_label">Type:</string>
<string name="profile_username_label">Username:</string>
<string name="profile_password_label">Password:</string>
<string name="profile_password_hint">(prompt when needed)</string>
+ <string name="profile_user_certificate_label">User certificate:</string>
+ <string name="profile_user_select_certificate_label">Select user certificate</string>
+ <string name="profile_user_select_certificate">Select a specific user certificate</string>
<string name="profile_ca_label">CA certificate:</string>
<string name="profile_ca_auto_label">Select automatically</string>
<string name="profile_ca_select_certificate_label">Select CA certificate</string>
diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java
index 053f91555..8323826d2 100644
--- a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java
+++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java
@@ -19,7 +19,8 @@ package org.strongswan.android.data;
public class VpnProfile implements Cloneable
{
- private String mName, mGateway, mUsername, mPassword, mCertificate;
+ private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate;
+ private VpnType mVpnType;
private long mId = -1;
public long getId()
@@ -52,6 +53,16 @@ public class VpnProfile implements Cloneable
this.mGateway = gateway;
}
+ public VpnType getVpnType()
+ {
+ return mVpnType;
+ }
+
+ public void setVpnType(VpnType type)
+ {
+ this.mVpnType = type;
+ }
+
public String getUsername()
{
return mUsername;
@@ -77,9 +88,19 @@ public class VpnProfile implements Cloneable
return mCertificate;
}
- public void setCertificateAlias(String certificate)
+ public void setCertificateAlias(String alias)
+ {
+ this.mCertificate = alias;
+ }
+
+ public String getUserCertificateAlias()
+ {
+ return mUserCertificate;
+ }
+
+ public void setUserCertificateAlias(String alias)
{
- this.mCertificate = certificate;
+ this.mUserCertificate = alias;
}
@Override
diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java
index 18632ad6f..6fd68d0c8 100644
--- a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java
+++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java
@@ -26,6 +26,7 @@ import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
import android.util.Log;
public class VpnProfileDataSource
@@ -34,9 +35,11 @@ public class VpnProfileDataSource
public static final String KEY_ID = "_id";
public static final String KEY_NAME = "name";
public static final String KEY_GATEWAY = "gateway";
+ public static final String KEY_VPN_TYPE = "vpn_type";
public static final String KEY_USERNAME = "username";
public static final String KEY_PASSWORD = "password";
public static final String KEY_CERTIFICATE = "certificate";
+ public static final String KEY_USER_CERTIFICATE = "user_certificate";
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDatabase;
@@ -45,24 +48,28 @@ public class VpnProfileDataSource
private static final String DATABASE_NAME = "strongswan.db";
private static final String TABLE_VPNPROFILE = "vpnprofile";
- private static final int DATABASE_VERSION = 1;
+ private static final int DATABASE_VERSION = 4;
public static final String DATABASE_CREATE =
"CREATE TABLE " + TABLE_VPNPROFILE + " (" +
KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
KEY_NAME + " TEXT NOT NULL," +
KEY_GATEWAY + " TEXT NOT NULL," +
- KEY_USERNAME + " TEXT NOT NULL," +
+ KEY_VPN_TYPE + " TEXT NOT NULL," +
+ KEY_USERNAME + " TEXT," +
KEY_PASSWORD + " TEXT," +
- KEY_CERTIFICATE + " TEXT" +
+ KEY_CERTIFICATE + " TEXT," +
+ KEY_USER_CERTIFICATE + " TEXT" +
");";
- private final String[] ALL_COLUMNS = new String[] {
+ private static final String[] ALL_COLUMNS = new String[] {
KEY_ID,
KEY_NAME,
KEY_GATEWAY,
+ KEY_VPN_TYPE,
KEY_USERNAME,
KEY_PASSWORD,
- KEY_CERTIFICATE
+ KEY_CERTIFICATE,
+ KEY_USER_CERTIFICATE,
};
private static class DatabaseHelper extends SQLiteOpenHelper
@@ -82,9 +89,40 @@ public class VpnProfileDataSource
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
Log.w(TAG, "Upgrading database from version " + oldVersion +
- " to " + newVersion + ", which will destroy all old data");
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_VPNPROFILE);
- onCreate(db);
+ " to " + newVersion);
+ if (oldVersion < 2)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE +
+ " TEXT;");
+ }
+ if (oldVersion < 3)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE +
+ " TEXT DEFAULT '';");
+ }
+ if (oldVersion < 4)
+ { /* remove NOT NULL constraint from username column */
+ updateColumns(db);
+ }
+ }
+
+ private void updateColumns(SQLiteDatabase db)
+ {
+ db.beginTransaction();
+ try
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";");
+ db.execSQL(DATABASE_CREATE);
+ StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT ");
+ SQLiteQueryBuilder.appendColumns(insert, ALL_COLUMNS);
+ db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString());
+ db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";");
+ db.setTransactionSuccessful();
+ }
+ finally
+ {
+ db.endTransaction();
+ }
}
}
@@ -212,9 +250,11 @@ public class VpnProfileDataSource
profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID)));
profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME)));
profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY)));
+ profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndex(KEY_VPN_TYPE))));
profile.setUsername(cursor.getString(cursor.getColumnIndex(KEY_USERNAME)));
profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD)));
profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE)));
+ profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_USER_CERTIFICATE)));
return profile;
}
@@ -223,9 +263,11 @@ public class VpnProfileDataSource
ContentValues values = new ContentValues();
values.put(KEY_NAME, profile.getName());
values.put(KEY_GATEWAY, profile.getGateway());
+ values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier());
values.put(KEY_USERNAME, profile.getUsername());
values.put(KEY_PASSWORD, profile.getPassword());
values.put(KEY_CERTIFICATE, profile.getCertificateAlias());
+ values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias());
return values;
}
}
diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnType.java b/src/frontends/android/src/org/strongswan/android/data/VpnType.java
new file mode 100644
index 000000000..44a4fa6b4
--- /dev/null
+++ b/src/frontends/android/src/org/strongswan/android/data/VpnType.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 Tobias Brunner
+ * Hochschule fuer Technik Rapperswil
+ *
+ * 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 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+package org.strongswan.android.data;
+
+public enum VpnType
+{
+ /* the order here must match the items in R.array.vpn_types */
+ IKEV2_EAP("ikev2-eap", true, false),
+ IKEV2_CERT("ikev2-cert", false, true);
+
+ private String mIdentifier;
+ private boolean mCertificate;
+ private boolean mUsernamePassword;
+
+ /**
+ * Enum which provides additional information about the supported VPN types.
+ *
+ * @param id identifier used to store and transmit this specific type
+ * @param userpass true if username and password are required
+ * @param certificate true if a client certificate is required
+ */
+ VpnType(String id, boolean userpass, boolean certificate)
+ {
+ mIdentifier = id;
+ mUsernamePassword = userpass;
+ mCertificate = certificate;
+ }
+
+ /**
+ * The identifier used to store this value in the database
+ * @return identifier
+ */
+ public String getIdentifier()
+ {
+ return mIdentifier;
+ }
+
+ /**
+ * Whether username and password are required for this type of VPN.
+ *
+ * @return true if username and password are required
+ */
+ public boolean getRequiresUsernamePassword()
+ {
+ return mUsernamePassword;
+ }
+
+ /**
+ * Whether a certificate is required for this type of VPN.
+ *
+ * @return true if a certificate is required
+ */
+ public boolean getRequiresCertificate()
+ {
+ return mCertificate;
+ }
+
+ /**
+ * Get the enum entry with the given identifier.
+ *
+ * @param identifier get the enum entry with this identifier
+ * @return the enum entry, or the default if not found
+ */
+ public static VpnType fromIdentifier(String identifier)
+ {
+ for (VpnType type : VpnType.values())
+ {
+ if (identifier.equals(type.mIdentifier))
+ {
+ return type;
+ }
+ }
+ return VpnType.IKEV2_EAP;
+ }
+}
diff --git a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java
index c9c1ad02a..9b502e89a 100644
--- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java
+++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java
@@ -21,6 +21,7 @@ import java.io.File;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
+import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
@@ -42,6 +43,8 @@ import android.net.VpnService;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.security.KeyChain;
+import android.security.KeyChainException;
import android.util.Log;
public class CharonVpnService extends VpnService implements Runnable
@@ -54,6 +57,7 @@ public class CharonVpnService extends VpnService implements Runnable
private Thread mConnectionHandler;
private VpnProfile mCurrentProfile;
private volatile String mCurrentCertificateAlias;
+ private volatile String mCurrentUserCertificateAlias;
private VpnProfile mNextProfile;
private volatile boolean mProfileUpdated;
private volatile boolean mTerminate;
@@ -203,6 +207,7 @@ public class CharonVpnService extends VpnService implements Runnable
/* store this in a separate (volatile) variable to avoid
* a possible deadlock during deinitialization */
mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias();
+ mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias();
setProfile(mCurrentProfile);
setError(ErrorState.NO_ERROR);
@@ -214,7 +219,8 @@ public class CharonVpnService extends VpnService implements Runnable
Log.i(TAG, "charon started");
String local_address = getLocalIPv4Address();
- initiate(local_address != null ? local_address : "0.0.0.0",
+ initiate(mCurrentProfile.getVpnType().getIdentifier(),
+ local_address != null ? local_address : "0.0.0.0",
mCurrentProfile.getGateway(), mCurrentProfile.getUsername(),
mCurrentProfile.getPassword());
}
@@ -421,6 +427,41 @@ public class CharonVpnService extends VpnService implements Runnable
}
/**
+ * Function called via JNI to get a list containing the DER encoded private key
+ * and DER encoded certificates of the user selected certificate chain (beginning
+ * with the user certificate).
+ *
+ * Since this method is called from a thread of charon's thread pool we are safe
+ * to call methods on KeyChain directly.
+ *
+ * @return list containing the private key and certificates (first element is the key)
+ * @throws InterruptedException
+ * @throws KeyChainException
+ * @throws CertificateEncodingException
+ */
+ private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException
+ {
+ ArrayList<byte[]> encodings = new ArrayList<byte[]>();
+ String alias = mCurrentUserCertificateAlias;
+ PrivateKey key = KeyChain.getPrivateKey(getApplicationContext(), alias);
+ if (key == null)
+ {
+ return null;
+ }
+ encodings.add(key.getEncoded());
+ X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), alias);
+ if (chain == null || chain.length == 0)
+ {
+ return null;
+ }
+ for (X509Certificate cert : chain)
+ {
+ encodings.add(cert.getEncoded());
+ }
+ return encodings.toArray(new byte[encodings.size()][]);
+ }
+
+ /**
* Initialization of charon, provided by libandroidbridge.so
*
* @param builder BuilderAdapter for this connection
@@ -436,7 +477,7 @@ public class CharonVpnService extends VpnService implements Runnable
/**
* Initiate VPN, provided by libandroidbridge.so
*/
- public native void initiate(String local_address, String gateway,
+ public native void initiate(String type, String local_address, String gateway,
String username, String password);
/**
diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java
index bc5030ea5..6ebfdcafc 100644
--- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java
+++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java
@@ -31,7 +31,6 @@ import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.ActivityNotFoundException;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.VpnService;
@@ -47,11 +46,9 @@ import android.widget.EditText;
public class MainActivity extends Activity implements OnVpnProfileSelectedListener
{
public static final String CONTACT_EMAIL = "android@strongswan.org";
- private static final String SHOW_ERROR_DIALOG = "errordialog";
private static final int PREPARE_VPN_SERVICE = 0;
- private VpnProfile activeProfile;
- private AlertDialog mErrorDialog;
+ private Bundle mProfileInfo;
@Override
public void onCreate(Bundle savedInstanceState)
@@ -63,33 +60,11 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
ActionBar bar = getActionBar();
bar.setDisplayShowTitleEnabled(false);
- if (savedInstanceState != null && savedInstanceState.getBoolean(SHOW_ERROR_DIALOG))
- {
- showVpnNotSupportedError();
- }
-
/* load CA certificates in a background task */
new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, false);
}
@Override
- protected void onSaveInstanceState(Bundle outState)
- {
- super.onSaveInstanceState(outState);
- outState.putBoolean(SHOW_ERROR_DIALOG, mErrorDialog != null);
- }
-
- @Override
- protected void onDestroy()
- {
- super.onDestroy();
- if (mErrorDialog != null)
- { /* avoid any errors about leaked windows */
- mErrorDialog.dismiss();
- }
- }
-
- @Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.main, menu);
@@ -116,10 +91,13 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
/**
* Prepare the VpnService. If this succeeds the current VPN profile is
* started.
+ * @param profileInfo a bundle containing the information about the profile to be started
*/
- protected void prepareVpnService()
+ protected void prepareVpnService(Bundle profileInfo)
{
Intent intent = VpnService.prepare(this);
+ /* store profile info until the user grants us permission */
+ mProfileInfo = profileInfo;
if (intent != null)
{
try
@@ -132,11 +110,11 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
* don't have the VPN components built into the system image.
* com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
* will not be found then */
- showVpnNotSupportedError();
+ new VpnNotSupportedError().show(getFragmentManager(), "ErrorDialog");
}
}
else
- {
+ { /* user already granted permission to use VpnService */
onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
}
}
@@ -147,12 +125,10 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
switch (requestCode)
{
case PREPARE_VPN_SERVICE:
- if (resultCode == RESULT_OK && activeProfile != null)
+ if (resultCode == RESULT_OK && mProfileInfo != null)
{
Intent intent = new Intent(this, CharonVpnService.class);
- intent.putExtra(VpnProfileDataSource.KEY_ID, activeProfile.getId());
- /* submit the password as the profile might not store one */
- intent.putExtra(VpnProfileDataSource.KEY_PASSWORD, activeProfile.getPassword());
+ intent.putExtras(mProfileInfo);
this.startService(intent);
}
break;
@@ -164,37 +140,24 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
@Override
public void onVpnProfileSelected(VpnProfile profile)
{
- activeProfile = profile;
- if (activeProfile.getPassword() == null)
+ Bundle profileInfo = new Bundle();
+ profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId());
+ profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername());
+ if (profile.getVpnType().getRequiresUsernamePassword() &&
+ profile.getPassword() == null)
{
- new LoginDialog().show(getFragmentManager(), "LoginDialog");
+ LoginDialog login = new LoginDialog();
+ login.setArguments(profileInfo);
+ login.show(getFragmentManager(), "LoginDialog");
}
else
{
- prepareVpnService();
+ profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword());
+ prepareVpnService(profileInfo);
}
}
/**
- * Show an error dialog if case the device lacks VPN support.
- */
- private void showVpnNotSupportedError()
- {
- mErrorDialog = new AlertDialog.Builder(this)
- .setTitle(R.string.vpn_not_supported_title)
- .setMessage(getString(R.string.vpn_not_supported))
- .setCancelable(false)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id)
- {
- mErrorDialog = null;
- dialog.dismiss();
- }
- }).show();
- }
-
- /**
* Class that loads or reloads the cached CA certificates.
*/
private class CertificateLoadTask extends AsyncTask<Boolean, Void, TrustedCertificateManager>
@@ -220,28 +183,32 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
}
}
- private class LoginDialog extends DialogFragment
+ /**
+ * Class that displays a login dialog and initiates the selected VPN
+ * profile if the user confirms the dialog.
+ */
+ public static class LoginDialog extends DialogFragment
{
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
- LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ final Bundle profileInfo = getArguments();
+ LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.login_dialog, null);
EditText username = (EditText)view.findViewById(R.id.username);
- username.setText(activeProfile.getUsername());
+ username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME));
final EditText password = (EditText)view.findViewById(R.id.password);
- Builder adb = new AlertDialog.Builder(MainActivity.this);
+ Builder adb = new AlertDialog.Builder(getActivity());
adb.setView(view);
adb.setTitle(getString(R.string.login_title));
adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton)
{
- /* let's work on a clone of the profile when updating the password */
- activeProfile = activeProfile.clone();
- activeProfile.setPassword(password.getText().toString().trim());
- prepareVpnService();
+ MainActivity activity = (MainActivity)getActivity();
+ profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim());
+ activity.prepareVpnService(profileInfo);
}
});
adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@@ -254,4 +221,27 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
return adb.create();
}
}
+
+ /**
+ * Class representing an error message which is displayed if VpnService is
+ * not supported on the current device.
+ */
+ public static class VpnNotSupportedError extends DialogFragment
+ {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.vpn_not_supported_title)
+ .setMessage(getString(R.string.vpn_not_supported))
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id)
+ {
+ dialog.dismiss();
+ }
+ }).create();
+ }
+ }
}
diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java
index 73365b40c..91e521cf4 100644
--- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -23,25 +23,34 @@ import org.strongswan.android.R;
import org.strongswan.android.data.TrustedCertificateEntry;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnType;
import org.strongswan.android.logic.TrustedCertificateManager;
import android.app.Activity;
import android.app.AlertDialog;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.security.KeyChainException;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
+import android.widget.Spinner;
+import android.widget.TwoLineListItem;
public class VpnProfileDetailActivity extends Activity
{
@@ -50,16 +59,20 @@ public class VpnProfileDetailActivity extends Activity
private VpnProfileDataSource mDataSource;
private Long mId;
private TrustedCertificateEntry mCertEntry;
+ private String mUserCertLoading;
+ private TrustedCertificateEntry mUserCertEntry;
+ private VpnType mVpnType = VpnType.IKEV2_EAP;
private VpnProfile mProfile;
private EditText mName;
private EditText mGateway;
+ private Spinner mSelectVpnType;
+ private ViewGroup mUsernamePassword;
private EditText mUsername;
private EditText mPassword;
+ private ViewGroup mUserCertificate;
+ private TwoLineListItem mSelectUserCert;
private CheckBox mCheckAuto;
- private RelativeLayout mSelectCert;
- private TextView mCertTitle;
- private TextView mCertSubtitle;
-
+ private TwoLineListItem mSelectCert;
@Override
public void onCreate(Bundle savedInstanceState)
@@ -75,14 +88,36 @@ public class VpnProfileDetailActivity extends Activity
setContentView(R.layout.profile_detail_view);
mName = (EditText)findViewById(R.id.name);
- mPassword = (EditText)findViewById(R.id.password);
mGateway = (EditText)findViewById(R.id.gateway);
+ mSelectVpnType = (Spinner)findViewById(R.id.vpn_type);
+
+ mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group);
mUsername = (EditText)findViewById(R.id.username);
+ mPassword = (EditText)findViewById(R.id.password);
+
+ mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group);
+ mSelectUserCert = (TwoLineListItem)findViewById(R.id.select_user_certificate);
mCheckAuto = (CheckBox)findViewById(R.id.ca_auto);
- mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate);
- mCertTitle = (TextView)findViewById(R.id.select_certificate_title);
- mCertSubtitle = (TextView)findViewById(R.id.select_certificate_subtitle);
+ mSelectCert = (TwoLineListItem)findViewById(R.id.select_certificate);
+
+ mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
+ {
+ mVpnType = VpnType.values()[position];
+ updateCredentialView();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent)
+ { /* should not happen */
+ mVpnType = VpnType.IKEV2_EAP;
+ updateCredentialView();
+ }
+ });
+
+ mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener());
mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
@@ -110,6 +145,7 @@ public class VpnProfileDetailActivity extends Activity
loadProfileData(savedInstanceState);
+ updateCredentialView();
updateCertificateSelector();
}
@@ -128,6 +164,10 @@ public class VpnProfileDetailActivity extends Activity
{
outState.putLong(VpnProfileDataSource.KEY_ID, mId);
}
+ if (mUserCertEntry != null)
+ {
+ outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias());
+ }
if (mCertEntry != null)
{
outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias());
@@ -179,6 +219,35 @@ public class VpnProfileDetailActivity extends Activity
}
/**
+ * Update the UI to enter credentials depending on the type of VPN currently selected
+ */
+ private void updateCredentialView()
+ {
+ mUsernamePassword.setVisibility(mVpnType.getRequiresUsernamePassword() ? View.VISIBLE : View.GONE);
+ mUserCertificate.setVisibility(mVpnType.getRequiresCertificate() ? View.VISIBLE : View.GONE);
+
+ if (mVpnType.getRequiresCertificate())
+ {
+ if (mUserCertLoading != null)
+ {
+ mSelectUserCert.getText1().setText(mUserCertLoading);
+ mSelectUserCert.getText2().setText(R.string.loading);
+ }
+ else if (mUserCertEntry != null)
+ { /* clear any errors and set the new data */
+ mSelectUserCert.getText1().setError(null);
+ mSelectUserCert.getText1().setText(mUserCertEntry.getAlias());
+ mSelectUserCert.getText2().setText(mUserCertEntry.getCertificate().getSubjectDN().toString());
+ }
+ else
+ {
+ mSelectUserCert.getText1().setText(R.string.profile_user_select_certificate_label);
+ mSelectUserCert.getText2().setText(R.string.profile_user_select_certificate);
+ }
+ }
+ }
+
+ /**
* Show an alert in case the previously selected certificate is not found anymore
* or the user did not select a certificate in the spinner.
*/
@@ -210,13 +279,13 @@ public class VpnProfileDetailActivity extends Activity
if (mCertEntry != null)
{
- mCertTitle.setText(mCertEntry.getSubjectPrimary());
- mCertSubtitle.setText(mCertEntry.getSubjectSecondary());
+ mSelectCert.getText1().setText(mCertEntry.getSubjectPrimary());
+ mSelectCert.getText2().setText(mCertEntry.getSubjectSecondary());
}
else
{
- mCertTitle.setText(R.string.profile_ca_select_certificate_label);
- mCertSubtitle.setText(R.string.profile_ca_select_certificate);
+ mSelectCert.getText1().setText(R.string.profile_ca_select_certificate_label);
+ mSelectCert.getText2().setText(R.string.profile_ca_select_certificate);
}
}
else
@@ -262,9 +331,17 @@ public class VpnProfileDetailActivity extends Activity
mGateway.setError(getString(R.string.alert_text_no_input_gateway));
valid = false;
}
- if (mUsername.getText().toString().trim().isEmpty())
+ if (mVpnType.getRequiresUsernamePassword())
{
- mUsername.setError(getString(R.string.alert_text_no_input_username));
+ if (mUsername.getText().toString().trim().isEmpty())
+ {
+ mUsername.setError(getString(R.string.alert_text_no_input_username));
+ valid = false;
+ }
+ }
+ if (mVpnType.getRequiresCertificate() && mUserCertEntry == null)
+ { /* let's show an error icon */
+ mSelectUserCert.getText1().setError("");
valid = false;
}
if (!mCheckAuto.isChecked() && mCertEntry == null)
@@ -285,10 +362,18 @@ public class VpnProfileDetailActivity extends Activity
String gateway = mGateway.getText().toString().trim();
mProfile.setName(name.isEmpty() ? gateway : name);
mProfile.setGateway(gateway);
- mProfile.setUsername(mUsername.getText().toString().trim());
- String password = mPassword.getText().toString().trim();
- password = password.isEmpty() ? null : password;
- mProfile.setPassword(password);
+ mProfile.setVpnType(mVpnType);
+ if (mVpnType.getRequiresUsernamePassword())
+ {
+ mProfile.setUsername(mUsername.getText().toString().trim());
+ String password = mPassword.getText().toString().trim();
+ password = password.isEmpty() ? null : password;
+ mProfile.setPassword(password);
+ }
+ if (mVpnType.getRequiresCertificate())
+ {
+ mProfile.setUserCertificateAlias(mUserCertEntry.getAlias());
+ }
String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias();
mProfile.setCertificateAlias(certAlias);
}
@@ -300,18 +385,20 @@ public class VpnProfileDetailActivity extends Activity
*/
private void loadProfileData(Bundle savedInstanceState)
{
- String alias = null;
+ String useralias = null, alias = null;
getActionBar().setTitle(R.string.add_profile);
- if (mId != null)
+ if (mId != null && mId != 0)
{
mProfile = mDataSource.getVpnProfile(mId);
if (mProfile != null)
{
mName.setText(mProfile.getName());
mGateway.setText(mProfile.getGateway());
+ mVpnType = mProfile.getVpnType();
mUsername.setText(mProfile.getUsername());
mPassword.setText(mProfile.getPassword());
+ useralias = mProfile.getUserCertificateAlias();
alias = mProfile.getCertificateAlias();
getActionBar().setTitle(mProfile.getName());
}
@@ -323,7 +410,18 @@ public class VpnProfileDetailActivity extends Activity
}
}
- /* check if the user selected a certificate previously */
+ mSelectVpnType.setSelection(mVpnType.ordinal());
+
+ /* check if the user selected a user certificate previously */
+ useralias = savedInstanceState == null ? useralias: savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
+ if (useralias != null)
+ {
+ UserCertificateLoader loader = new UserCertificateLoader(this, useralias);
+ mUserCertLoading = useralias;
+ loader.execute();
+ }
+
+ /* check if the user selected a CA certificate previously */
alias = savedInstanceState == null ? alias : savedInstanceState.getString(VpnProfileDataSource.KEY_CERTIFICATE);
mCheckAuto.setChecked(alias == null);
if (alias != null)
@@ -340,4 +438,102 @@ public class VpnProfileDetailActivity extends Activity
}
}
}
+
+ private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback
+ {
+ @Override
+ public void onClick(View v)
+ {
+ String useralias = mUserCertEntry != null ? mUserCertEntry.getAlias() : null;
+ KeyChain.choosePrivateKeyAlias(VpnProfileDetailActivity.this, this, new String[] { "RSA" }, null, null, -1, useralias);
+ }
+
+ @Override
+ public void alias(final String alias)
+ {
+ if (alias != null)
+ { /* otherwise the dialog was canceled, the request denied */
+ try
+ {
+ final X509Certificate[] chain = KeyChain.getCertificateChain(VpnProfileDetailActivity.this, alias);
+ /* alias() is not called from our main thread */
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run()
+ {
+ if (chain != null && chain.length > 0)
+ {
+ mUserCertEntry = new TrustedCertificateEntry(alias, chain[0]);
+ }
+ updateCredentialView();
+ }
+ });
+ }
+ catch (KeyChainException e)
+ {
+ e.printStackTrace();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * Load the selected user certificate asynchronously. This cannot be done
+ * from the main thread as getCertificateChain() calls back to our main
+ * thread to bind to the KeyChain service resulting in a deadlock.
+ */
+ private class UserCertificateLoader extends AsyncTask<Void, Void, X509Certificate>
+ {
+ private final Context mContext;
+ private final String mAlias;
+
+ public UserCertificateLoader(Context context, String alias)
+ {
+ mContext = context;
+ mAlias = alias;
+ }
+
+ @Override
+ protected X509Certificate doInBackground(Void... params)
+ {
+ X509Certificate[] chain = null;
+ try
+ {
+ chain = KeyChain.getCertificateChain(mContext, mAlias);
+ }
+ catch (KeyChainException e)
+ {
+ e.printStackTrace();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ if (chain != null && chain.length > 0)
+ {
+ return chain[0];
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(X509Certificate result)
+ {
+ if (result != null)
+ {
+ mUserCertEntry = new TrustedCertificateEntry(mAlias, result);
+ }
+ else
+ { /* previously selected certificate is not here anymore */
+ mSelectUserCert.getText1().setError("");
+ mUserCertEntry = null;
+ }
+ mUserCertLoading = null;
+ updateCredentialView();
+ }
+ }
}
diff --git a/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
index 39e3e586a..85dc8370a 100644
--- a/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
+++ b/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
@@ -64,7 +64,25 @@ public class VpnProfileAdapter extends ArrayAdapter<VpnProfile>
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway);
tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway());
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username);
- tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername());
+ if (profile.getVpnType().getRequiresUsernamePassword())
+ { /* if the view is reused we make sure it is visible */
+ tv.setVisibility(View.VISIBLE);
+ tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername());
+ }
+ else
+ {
+ tv.setVisibility(View.GONE);
+ }
+ tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_certificate);
+ if (profile.getVpnType().getRequiresCertificate())
+ {
+ tv.setText(getContext().getString(R.string.profile_user_certificate_label) + " " + profile.getUserCertificateAlias());
+ tv.setVisibility(View.VISIBLE);
+ }
+ else
+ {
+ tv.setVisibility(View.GONE);
+ }
return vpnProfileView;
}
diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk
index 3b2d7eaaa..9125079a4 100644
--- a/src/libstrongswan/Android.mk
+++ b/src/libstrongswan/Android.mk
@@ -67,6 +67,8 @@ LOCAL_SRC_FILES += $(call add_plugin, pem)
LOCAL_SRC_FILES += $(call add_plugin, pkcs1)
+LOCAL_SRC_FILES += $(call add_plugin, pkcs8)
+
LOCAL_SRC_FILES += $(call add_plugin, pkcs11)
LOCAL_SRC_FILES += $(call add_plugin, pubkey)