Merge branch 'android-client-cert'

Introduces IKEv2 client certificate authentication for the Android App.
This commit is contained in:
Tobias Brunner
2012-09-04 13:58:49 +02:00
26 changed files with 931 additions and 183 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ include $(CLEAR_VARS)
# this is the list of plugins that are built into libstrongswan and charon # this is the list of plugins that are built into libstrongswan and charon
# also these plugins are loaded by default (if not changed in strongswan.conf) # also these plugins are loaded by default (if not changed in strongswan.conf)
strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \
pkcs1 pem xcbc hmac kernel-netlink socket-default android \ pkcs1 pkcs8 pem xcbc hmac kernel-netlink socket-default android \
stroke eap-identity eap-mschapv2 eap-md5 eap-gtc stroke eap-identity eap-mschapv2 eap-md5 eap-gtc
ifneq ($(strongswan_BUILD_SCEPCLIENT),) ifneq ($(strongswan_BUILD_SCEPCLIENT),)
+1 -1
View File
@@ -2,7 +2,7 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS) include $(CLEAR_VARS)
strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ 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 eap-identity eap-mschapv2 eap-md5 eap-gtc
strongswan_PLUGINS := $(strongswan_CHARON_PLUGINS) strongswan_PLUGINS := $(strongswan_CHARON_PLUGINS)
@@ -90,13 +90,16 @@ static inline bool androidjni_exception_occurred(JNIEnv *env)
*/ */
static inline char *androidjni_convert_jstring(JNIEnv *env, jstring jstr) static inline char *androidjni_convert_jstring(JNIEnv *env, jstring jstr)
{ {
char *str; char *str = NULL;
jsize len; jsize len;
len = (*env)->GetStringUTFLength(env, jstr); if (jstr)
str = malloc(len + 1); {
(*env)->GetStringUTFRegion(env, jstr, 0, len, str); len = (*env)->GetStringUTFLength(env, jstr);
str[len] = '\0'; str = malloc(len + 1);
(*env)->GetStringUTFRegion(env, jstr, 0, len, str);
str[len] = '\0';
}
return str; return str;
} }
@@ -49,6 +49,15 @@ struct private_android_creds_t {
bool loaded; bool loaded;
}; };
/**
* Free allocated DER encoding
*/
static void free_encoding(chunk_t *chunk)
{
chunk_free(chunk);
free(chunk);
}
/** /**
* Load trusted certificates via charonservice (JNI). * Load trusted certificates via charonservice (JNI).
*/ */
@@ -71,8 +80,7 @@ static void load_trusted_certificates(private_android_creds_t *this)
cert->get_subject(cert)); cert->get_subject(cert));
this->creds->add_cert(this->creds, TRUE, cert); this->creds->add_cert(this->creds, TRUE, cert);
} }
chunk_free(current); free_encoding(current);
free(current);
} }
certs->destroy(certs); certs->destroy(certs);
} }
@@ -130,6 +138,76 @@ METHOD(credential_set_t, create_shared_enumerator, enumerator_t*,
type, me, other); 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, METHOD(android_creds_t, clear, void,
private_android_creds_t *this) private_android_creds_t *this)
{ {
@@ -160,11 +238,12 @@ android_creds_t *android_creds_create()
.set = { .set = {
.create_cert_enumerator = _create_cert_enumerator, .create_cert_enumerator = _create_cert_enumerator,
.create_shared_enumerator = _create_shared_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, .create_cdp_enumerator = (void*)return_null,
.cache_cert = (void*)nop, .cache_cert = (void*)nop,
}, },
.add_username_password = _add_username_password, .add_username_password = _add_username_password,
.load_user_certificate = _load_user_certificate,
.clear = _clear, .clear = _clear,
.destroy = _destroy, .destroy = _destroy,
}, },
@@ -46,6 +46,13 @@ struct android_creds_t {
void (*add_username_password)(android_creds_t *this, char *username, void (*add_username_password)(android_creds_t *this, char *username,
char *password); 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. * Clear the cached certificates and stored credentials.
*/ */
@@ -43,11 +43,21 @@ struct private_android_service_t {
*/ */
android_service_t public; android_service_t public;
/**
* credential set
*/
android_creds_t *creds;
/** /**
* current IKE_SA * current IKE_SA
*/ */
ike_sa_t *ike_sa; ike_sa_t *ike_sa;
/**
* the type of VPN
*/
char *type;
/** /**
* local ipv4 address * local ipv4 address
*/ */
@@ -63,6 +73,11 @@ struct private_android_service_t {
*/ */
char *username; char *username;
/**
* password
*/
char *password;
/** /**
* lock to safely access the TUN device fd * lock to safely access the TUN device fd
*/ */
@@ -445,11 +460,42 @@ static job_requeue_t initiate(private_android_service_t *this)
FALSE, NULL, NULL); /* mediation */ FALSE, NULL, NULL); /* mediation */
peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("0.0.0.0", 0)); peer_cfg->add_virtual_ip(peer_cfg, host_create_from_string("0.0.0.0", 0));
auth = auth_cfg_create(); /* local auth config */
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); if (streq("ikev2-eap", this->type))
user = identification_create_from_string(this->username); {
auth->add(auth, AUTH_RULE_IDENTITY, user); auth = auth_cfg_create();
peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); 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 = auth_cfg_create();
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
gateway = identification_create_from_string(this->gateway); 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 */ /* make sure the tun device is actually closed */
close_tun_device(this); close_tun_device(this);
this->lock->destroy(this->lock); this->lock->destroy(this->lock);
free(this->type);
free(this->local_address); free(this->local_address);
free(this->username);
free(this->gateway); free(this->gateway);
free(this->username);
if (this->password)
{
memwipe(this->password, strlen(this->password));
free(this->password);
}
free(this); free(this);
} }
/** /**
* See header * See header
*/ */
android_service_t *android_service_create(char *local_address, char *gateway, android_service_t *android_service_create(android_creds_t *creds, char *type,
char *username) char *local_address, char *gateway,
char *username, char *password)
{ {
private_android_service_t *this; 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), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
.local_address = local_address, .local_address = local_address,
.username = username, .username = username,
.password = password,
.gateway = gateway, .gateway = gateway,
.creds = creds,
.type = type,
.tunfd = -1, .tunfd = -1,
); );
@@ -51,11 +51,15 @@ struct android_service_t {
* Create an Android service instance. Queues a job that starts initiation of a * Create an Android service instance. Queues a job that starts initiation of a
* new IKE SA. * new IKE SA.
* *
* @param creds Android specific credential set
* @param type VPN type (see VpnType.java)
* @param local_address local ip address * @param local_address local ip address
* @param gateway gateway address * @param gateway gateway address
* @param username user name (local identity) * @param username user name (local identity)
* @param password password (if any)
*/ */
android_service_t *android_service_create(char *local_address, char *gateway, android_service_t *android_service_create(android_creds_t *creds, char *type,
char *username); char *local_address, char *gateway,
char *username, char *password);
#endif /** ANDROID_SERVICE_H_ @}*/ #endif /** ANDROID_SERVICE_H_ @}*/
@@ -199,6 +199,33 @@ failed:
return FALSE; 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*, METHOD(charonservice_t, get_trusted_certificates, linked_list_t*,
private_charonservice_t *this) private_charonservice_t *this)
{ {
@@ -206,7 +233,6 @@ METHOD(charonservice_t, get_trusted_certificates, linked_list_t*,
jmethodID method_id; jmethodID method_id;
jobjectArray jcerts; jobjectArray jcerts;
linked_list_t *list; linked_list_t *list;
jsize i;
androidjni_attach_thread(&env); androidjni_attach_thread(&env);
@@ -222,21 +248,39 @@ METHOD(charonservice_t, get_trusted_certificates, linked_list_t*,
{ {
goto failed; goto failed;
} }
list = linked_list_create(); list = convert_array_of_byte_arrays(env, jcerts);
for (i = 0; i < (*env)->GetArrayLength(env, jcerts); ++i) androidjni_detach_thread();
return list;
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);
method_id = (*env)->GetMethodID(env,
android_charonvpnservice_class,
"getUserCertificate", "()[[B");
if (!method_id)
{ {
chunk_t *ca_cert; goto failed;
jbyteArray jcert;
ca_cert = malloc_thing(chunk_t);
list->insert_last(list, ca_cert);
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);
} }
(*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(); androidjni_detach_thread();
return list; return list;
@@ -260,17 +304,15 @@ METHOD(charonservice_t, get_vpnservice_builder, vpnservice_builder_t*,
* @param username username (gets owned) * @param username username (gets owned)
* @param password password (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; private_charonservice_t *this = (private_charonservice_t*)charonservice;
this->creds->clear(this->creds); this->creds->clear(this->creds);
this->creds->add_username_password(this->creds, username, password);
memwipe(password, strlen(password));
free(password);
DESTROY_IF(this->service); 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, .update_status = _update_status,
.bypass_socket = _bypass_socket, .bypass_socket = _bypass_socket,
.get_trusted_certificates = _get_trusted_certificates, .get_trusted_certificates = _get_trusted_certificates,
.get_user_certificate = _get_user_certificate,
.get_vpnservice_builder = _get_vpnservice_builder, .get_vpnservice_builder = _get_vpnservice_builder,
}, },
.attr = android_attr_create(), .attr = android_attr_create(),
@@ -477,15 +520,16 @@ JNI_METHOD(CharonVpnService, deinitializeCharon, void)
* Initiate SA * Initiate SA
*/ */
JNI_METHOD(CharonVpnService, initiate, void, JNI_METHOD(CharonVpnService, initiate, void,
jstring jlocal_address, jstring jgateway, jstring jusername, jstring jtype, jstring jlocal_address, jstring jgateway, jstring jusername,
jstring jpassword) 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); local_address = androidjni_convert_jstring(env, jlocal_address);
gateway = androidjni_convert_jstring(env, jgateway); gateway = androidjni_convert_jstring(env, jgateway);
username = androidjni_convert_jstring(env, jusername); username = androidjni_convert_jstring(env, jusername);
password = androidjni_convert_jstring(env, jpassword); password = androidjni_convert_jstring(env, jpassword);
initiate(local_address, gateway, username, password); initiate(type, local_address, gateway, username, password);
} }
@@ -85,6 +85,17 @@ struct charonservice_t {
*/ */
linked_list_t *(*get_trusted_certificates)(charonservice_t *this); 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 * Get the current vpnservice_builder_t object
* *
@@ -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>
@@ -56,28 +56,67 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:text="@string/profile_username_label" /> android:text="@string/profile_vpn_type_label" />
<EditText <Spinner
android:id="@+id/username" android:id="@+id/vpn_type"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:spinnerMode="dropdown"
android:inputType="textNoSuggestions" /> android:entries="@array/vpn_types" />
<TextView <LinearLayout
android:id="@+id/username_password_group"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:orientation="vertical" >
android:text="@string/profile_password_label" />
<EditText <TextView
android:id="@+id/password" 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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:orientation="vertical" >
android:inputType="textPassword|textNoSuggestions"
android:hint="@string/profile_password_hint" /> <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 <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -91,32 +130,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/profile_ca_auto_label" /> android:text="@string/profile_ca_auto_label" />
<RelativeLayout <include
android:id="@+id/select_certificate" android:id="@+id/select_certificate"
android:layout_width="match_parent" layout="@layout/certificate_selector" />
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>
</LinearLayout> </LinearLayout>
@@ -46,4 +46,14 @@
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_marginLeft="15dp" /> 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> </LinearLayout>
@@ -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>
@@ -25,6 +25,7 @@
<string name="search">Suchen</string> <string name="search">Suchen</string>
<string name="vpn_not_supported_title">VPN nicht unterstützt</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="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 --> <!-- Log view -->
<string name="log_title">Log</string> <string name="log_title">Log</string>
@@ -49,9 +50,13 @@
<string name="profile_name_label">Profilname:</string> <string name="profile_name_label">Profilname:</string>
<string name="profile_name_hint">(Gateway-Adresse verwenden)</string> <string name="profile_name_hint">(Gateway-Adresse verwenden)</string>
<string name="profile_gateway_label">Gateway:</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_username_label">Benutzername:</string>
<string name="profile_password_label">Passwort:</string> <string name="profile_password_label">Passwort:</string>
<string name="profile_password_hint">(anfordern wenn benötigt)</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_label">CA-Zertifikat:</string>
<string name="profile_ca_auto_label">Automatisch wählen</string> <string name="profile_ca_auto_label">Automatisch wählen</string>
<string name="profile_ca_select_certificate_label">CA-Zertifikat auswählen</string> <string name="profile_ca_select_certificate_label">CA-Zertifikat auswählen</string>
@@ -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>
@@ -27,6 +27,7 @@
<string name="search">Szukaj</string> <string name="search">Szukaj</string>
<string name="vpn_not_supported_title">Nie obsługiwany VPN</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="vpn_not_supported">Urządzenie nie obsługuje aplikacji VPN.\nProszę skontaktować się z producentem.</string>
<string name="loading">Wczytywanie&#8230;</string>
<!-- Log view --> <!-- Log view -->
<string name="log_title">Log</string> <string name="log_title">Log</string>
@@ -51,9 +52,13 @@
<string name="profile_name_label">Nazwa profilu:</string> <string name="profile_name_label">Nazwa profilu:</string>
<string name="profile_name_hint">(użyj adresu bramki)</string> <string name="profile_name_hint">(użyj adresu bramki)</string>
<string name="profile_gateway_label">Bramka:</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_username_label">Użytkownik:</string>
<string name="profile_password_label">Hasło:</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_label">Certyfikat CA:</string>
<string name="profile_ca_auto_label">Wybierz automatycznie</string> <string name="profile_ca_auto_label">Wybierz automatycznie</string>
<string name="profile_ca_select_certificate_label">Wybierz certyfikat CA</string> <string name="profile_ca_select_certificate_label">Wybierz certyfikat CA</string>
@@ -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>
@@ -25,6 +25,7 @@
<string name="search">Search</string> <string name="search">Search</string>
<string name="vpn_not_supported_title">VPN not supported</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="vpn_not_supported">Your device does not support VPN applications.\nPlease contact the manufacturer.</string>
<string name="loading">Loading&#8230;</string>
<!-- Log view --> <!-- Log view -->
<string name="log_title">Log</string> <string name="log_title">Log</string>
@@ -49,9 +50,13 @@
<string name="profile_name_label">Profile Name:</string> <string name="profile_name_label">Profile Name:</string>
<string name="profile_name_hint">(use gateway address)</string> <string name="profile_name_hint">(use gateway address)</string>
<string name="profile_gateway_label">Gateway:</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_username_label">Username:</string>
<string name="profile_password_label">Password:</string> <string name="profile_password_label">Password:</string>
<string name="profile_password_hint">(prompt when needed)</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_label">CA certificate:</string>
<string name="profile_ca_auto_label">Select automatically</string> <string name="profile_ca_auto_label">Select automatically</string>
<string name="profile_ca_select_certificate_label">Select CA certificate</string> <string name="profile_ca_select_certificate_label">Select CA certificate</string>
@@ -19,7 +19,8 @@ package org.strongswan.android.data;
public class VpnProfile implements Cloneable 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; private long mId = -1;
public long getId() public long getId()
@@ -52,6 +53,16 @@ public class VpnProfile implements Cloneable
this.mGateway = gateway; this.mGateway = gateway;
} }
public VpnType getVpnType()
{
return mVpnType;
}
public void setVpnType(VpnType type)
{
this.mVpnType = type;
}
public String getUsername() public String getUsername()
{ {
return mUsername; return mUsername;
@@ -77,9 +88,19 @@ public class VpnProfile implements Cloneable
return mCertificate; return mCertificate;
} }
public void setCertificateAlias(String certificate) public void setCertificateAlias(String alias)
{ {
this.mCertificate = certificate; this.mCertificate = alias;
}
public String getUserCertificateAlias()
{
return mUserCertificate;
}
public void setUserCertificateAlias(String alias)
{
this.mUserCertificate = alias;
} }
@Override @Override
@@ -26,6 +26,7 @@ import android.database.Cursor;
import android.database.SQLException; import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.util.Log; import android.util.Log;
public class VpnProfileDataSource public class VpnProfileDataSource
@@ -34,9 +35,11 @@ public class VpnProfileDataSource
public static final String KEY_ID = "_id"; public static final String KEY_ID = "_id";
public static final String KEY_NAME = "name"; public static final String KEY_NAME = "name";
public static final String KEY_GATEWAY = "gateway"; 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_USERNAME = "username";
public static final String KEY_PASSWORD = "password"; public static final String KEY_PASSWORD = "password";
public static final String KEY_CERTIFICATE = "certificate"; public static final String KEY_CERTIFICATE = "certificate";
public static final String KEY_USER_CERTIFICATE = "user_certificate";
private DatabaseHelper mDbHelper; private DatabaseHelper mDbHelper;
private SQLiteDatabase mDatabase; private SQLiteDatabase mDatabase;
@@ -45,24 +48,28 @@ public class VpnProfileDataSource
private static final String DATABASE_NAME = "strongswan.db"; private static final String DATABASE_NAME = "strongswan.db";
private static final String TABLE_VPNPROFILE = "vpnprofile"; 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 = public static final String DATABASE_CREATE =
"CREATE TABLE " + TABLE_VPNPROFILE + " (" + "CREATE TABLE " + TABLE_VPNPROFILE + " (" +
KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
KEY_NAME + " TEXT NOT NULL," + KEY_NAME + " TEXT NOT NULL," +
KEY_GATEWAY + " 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_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_ID,
KEY_NAME, KEY_NAME,
KEY_GATEWAY, KEY_GATEWAY,
KEY_VPN_TYPE,
KEY_USERNAME, KEY_USERNAME,
KEY_PASSWORD, KEY_PASSWORD,
KEY_CERTIFICATE KEY_CERTIFICATE,
KEY_USER_CERTIFICATE,
}; };
private static class DatabaseHelper extends SQLiteOpenHelper private static class DatabaseHelper extends SQLiteOpenHelper
@@ -82,9 +89,40 @@ public class VpnProfileDataSource
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{ {
Log.w(TAG, "Upgrading database from version " + oldVersion + Log.w(TAG, "Upgrading database from version " + oldVersion +
" to " + newVersion + ", which will destroy all old data"); " to " + newVersion);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_VPNPROFILE); if (oldVersion < 2)
onCreate(db); {
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.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID)));
profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME))); profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME)));
profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY))); 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.setUsername(cursor.getString(cursor.getColumnIndex(KEY_USERNAME)));
profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD))); profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD)));
profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE))); profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE)));
profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_USER_CERTIFICATE)));
return profile; return profile;
} }
@@ -223,9 +263,11 @@ public class VpnProfileDataSource
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(KEY_NAME, profile.getName()); values.put(KEY_NAME, profile.getName());
values.put(KEY_GATEWAY, profile.getGateway()); values.put(KEY_GATEWAY, profile.getGateway());
values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier());
values.put(KEY_USERNAME, profile.getUsername()); values.put(KEY_USERNAME, profile.getUsername());
values.put(KEY_PASSWORD, profile.getPassword()); values.put(KEY_PASSWORD, profile.getPassword());
values.put(KEY_CERTIFICATE, profile.getCertificateAlias()); values.put(KEY_CERTIFICATE, profile.getCertificateAlias());
values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias());
return values; return values;
} }
} }
@@ -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;
}
}
@@ -21,6 +21,7 @@ import java.io.File;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
@@ -42,6 +43,8 @@ import android.net.VpnService;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.security.KeyChain;
import android.security.KeyChainException;
import android.util.Log; import android.util.Log;
public class CharonVpnService extends VpnService implements Runnable public class CharonVpnService extends VpnService implements Runnable
@@ -54,6 +57,7 @@ public class CharonVpnService extends VpnService implements Runnable
private Thread mConnectionHandler; private Thread mConnectionHandler;
private VpnProfile mCurrentProfile; private VpnProfile mCurrentProfile;
private volatile String mCurrentCertificateAlias; private volatile String mCurrentCertificateAlias;
private volatile String mCurrentUserCertificateAlias;
private VpnProfile mNextProfile; private VpnProfile mNextProfile;
private volatile boolean mProfileUpdated; private volatile boolean mProfileUpdated;
private volatile boolean mTerminate; private volatile boolean mTerminate;
@@ -203,6 +207,7 @@ public class CharonVpnService extends VpnService implements Runnable
/* store this in a separate (volatile) variable to avoid /* store this in a separate (volatile) variable to avoid
* a possible deadlock during deinitialization */ * a possible deadlock during deinitialization */
mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias(); mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias();
mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias();
setProfile(mCurrentProfile); setProfile(mCurrentProfile);
setError(ErrorState.NO_ERROR); setError(ErrorState.NO_ERROR);
@@ -214,7 +219,8 @@ public class CharonVpnService extends VpnService implements Runnable
Log.i(TAG, "charon started"); Log.i(TAG, "charon started");
String local_address = getLocalIPv4Address(); 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.getGateway(), mCurrentProfile.getUsername(),
mCurrentProfile.getPassword()); mCurrentProfile.getPassword());
} }
@@ -420,6 +426,41 @@ public class CharonVpnService extends VpnService implements Runnable
return certs.toArray(new byte[certs.size()][]); return certs.toArray(new byte[certs.size()][]);
} }
/**
* 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 * Initialization of charon, provided by libandroidbridge.so
* *
@@ -436,7 +477,7 @@ public class CharonVpnService extends VpnService implements Runnable
/** /**
* Initiate VPN, provided by libandroidbridge.so * 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); String username, String password);
/** /**
@@ -31,7 +31,6 @@ import android.app.AlertDialog.Builder;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.VpnService; import android.net.VpnService;
@@ -47,11 +46,9 @@ import android.widget.EditText;
public class MainActivity extends Activity implements OnVpnProfileSelectedListener public class MainActivity extends Activity implements OnVpnProfileSelectedListener
{ {
public static final String CONTACT_EMAIL = "[email protected]"; public static final String CONTACT_EMAIL = "[email protected]";
private static final String SHOW_ERROR_DIALOG = "errordialog";
private static final int PREPARE_VPN_SERVICE = 0; private static final int PREPARE_VPN_SERVICE = 0;
private VpnProfile activeProfile; private Bundle mProfileInfo;
private AlertDialog mErrorDialog;
@Override @Override
public void onCreate(Bundle savedInstanceState) public void onCreate(Bundle savedInstanceState)
@@ -63,32 +60,10 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
ActionBar bar = getActionBar(); ActionBar bar = getActionBar();
bar.setDisplayShowTitleEnabled(false); bar.setDisplayShowTitleEnabled(false);
if (savedInstanceState != null && savedInstanceState.getBoolean(SHOW_ERROR_DIALOG))
{
showVpnNotSupportedError();
}
/* load CA certificates in a background task */ /* load CA certificates in a background task */
new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, false); 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 @Override
public boolean onCreateOptionsMenu(Menu menu) public boolean onCreateOptionsMenu(Menu menu)
{ {
@@ -116,10 +91,13 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
/** /**
* Prepare the VpnService. If this succeeds the current VPN profile is * Prepare the VpnService. If this succeeds the current VPN profile is
* started. * 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); Intent intent = VpnService.prepare(this);
/* store profile info until the user grants us permission */
mProfileInfo = profileInfo;
if (intent != null) if (intent != null)
{ {
try try
@@ -132,11 +110,11 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
* don't have the VPN components built into the system image. * don't have the VPN components built into the system image.
* com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog
* will not be found then */ * will not be found then */
showVpnNotSupportedError(); new VpnNotSupportedError().show(getFragmentManager(), "ErrorDialog");
} }
} }
else else
{ { /* user already granted permission to use VpnService */
onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null); onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
} }
} }
@@ -147,12 +125,10 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
switch (requestCode) switch (requestCode)
{ {
case PREPARE_VPN_SERVICE: case PREPARE_VPN_SERVICE:
if (resultCode == RESULT_OK && activeProfile != null) if (resultCode == RESULT_OK && mProfileInfo != null)
{ {
Intent intent = new Intent(this, CharonVpnService.class); Intent intent = new Intent(this, CharonVpnService.class);
intent.putExtra(VpnProfileDataSource.KEY_ID, activeProfile.getId()); intent.putExtras(mProfileInfo);
/* submit the password as the profile might not store one */
intent.putExtra(VpnProfileDataSource.KEY_PASSWORD, activeProfile.getPassword());
this.startService(intent); this.startService(intent);
} }
break; break;
@@ -164,36 +140,23 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
@Override @Override
public void onVpnProfileSelected(VpnProfile profile) public void onVpnProfileSelected(VpnProfile profile)
{ {
activeProfile = profile; Bundle profileInfo = new Bundle();
if (activeProfile.getPassword() == null) 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 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. * Class that loads or reloads the cached CA certificates.
*/ */
@@ -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 @Override
public Dialog onCreateDialog(Bundle savedInstanceState) 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); View view = inflater.inflate(R.layout.login_dialog, null);
EditText username = (EditText)view.findViewById(R.id.username); 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); 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.setView(view);
adb.setTitle(getString(R.string.login_title)); adb.setTitle(getString(R.string.login_title));
adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() { adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int whichButton) public void onClick(DialogInterface dialog, int whichButton)
{ {
/* let's work on a clone of the profile when updating the password */ MainActivity activity = (MainActivity)getActivity();
activeProfile = activeProfile.clone(); profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim());
activeProfile.setPassword(password.getText().toString().trim()); activity.prepareVpnService(profileInfo);
prepareVpnService();
} }
}); });
adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@@ -254,4 +221,27 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
return adb.create(); 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();
}
}
} }
@@ -23,25 +23,34 @@ import org.strongswan.android.R;
import org.strongswan.android.data.TrustedCertificateEntry; import org.strongswan.android.data.TrustedCertificateEntry;
import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfileDataSource; import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.data.VpnType;
import org.strongswan.android.logic.TrustedCertificateManager; import org.strongswan.android.logic.TrustedCertificateManager;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.security.KeyChainException;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; 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.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText; import android.widget.EditText;
import android.widget.RelativeLayout; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TwoLineListItem;
public class VpnProfileDetailActivity extends Activity public class VpnProfileDetailActivity extends Activity
{ {
@@ -50,16 +59,20 @@ public class VpnProfileDetailActivity extends Activity
private VpnProfileDataSource mDataSource; private VpnProfileDataSource mDataSource;
private Long mId; private Long mId;
private TrustedCertificateEntry mCertEntry; private TrustedCertificateEntry mCertEntry;
private String mUserCertLoading;
private TrustedCertificateEntry mUserCertEntry;
private VpnType mVpnType = VpnType.IKEV2_EAP;
private VpnProfile mProfile; private VpnProfile mProfile;
private EditText mName; private EditText mName;
private EditText mGateway; private EditText mGateway;
private Spinner mSelectVpnType;
private ViewGroup mUsernamePassword;
private EditText mUsername; private EditText mUsername;
private EditText mPassword; private EditText mPassword;
private ViewGroup mUserCertificate;
private TwoLineListItem mSelectUserCert;
private CheckBox mCheckAuto; private CheckBox mCheckAuto;
private RelativeLayout mSelectCert; private TwoLineListItem mSelectCert;
private TextView mCertTitle;
private TextView mCertSubtitle;
@Override @Override
public void onCreate(Bundle savedInstanceState) public void onCreate(Bundle savedInstanceState)
@@ -75,14 +88,36 @@ public class VpnProfileDetailActivity extends Activity
setContentView(R.layout.profile_detail_view); setContentView(R.layout.profile_detail_view);
mName = (EditText)findViewById(R.id.name); mName = (EditText)findViewById(R.id.name);
mPassword = (EditText)findViewById(R.id.password);
mGateway = (EditText)findViewById(R.id.gateway); 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); 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); mCheckAuto = (CheckBox)findViewById(R.id.ca_auto);
mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate); mSelectCert = (TwoLineListItem)findViewById(R.id.select_certificate);
mCertTitle = (TextView)findViewById(R.id.select_certificate_title);
mCertSubtitle = (TextView)findViewById(R.id.select_certificate_subtitle); 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() { mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override @Override
@@ -110,6 +145,7 @@ public class VpnProfileDetailActivity extends Activity
loadProfileData(savedInstanceState); loadProfileData(savedInstanceState);
updateCredentialView();
updateCertificateSelector(); updateCertificateSelector();
} }
@@ -128,6 +164,10 @@ public class VpnProfileDetailActivity extends Activity
{ {
outState.putLong(VpnProfileDataSource.KEY_ID, mId); outState.putLong(VpnProfileDataSource.KEY_ID, mId);
} }
if (mUserCertEntry != null)
{
outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias());
}
if (mCertEntry != null) if (mCertEntry != null)
{ {
outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias()); outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias());
@@ -178,6 +218,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 * Show an alert in case the previously selected certificate is not found anymore
* or the user did not select a certificate in the spinner. * or the user did not select a certificate in the spinner.
@@ -210,13 +279,13 @@ public class VpnProfileDetailActivity extends Activity
if (mCertEntry != null) if (mCertEntry != null)
{ {
mCertTitle.setText(mCertEntry.getSubjectPrimary()); mSelectCert.getText1().setText(mCertEntry.getSubjectPrimary());
mCertSubtitle.setText(mCertEntry.getSubjectSecondary()); mSelectCert.getText2().setText(mCertEntry.getSubjectSecondary());
} }
else else
{ {
mCertTitle.setText(R.string.profile_ca_select_certificate_label); mSelectCert.getText1().setText(R.string.profile_ca_select_certificate_label);
mCertSubtitle.setText(R.string.profile_ca_select_certificate); mSelectCert.getText2().setText(R.string.profile_ca_select_certificate);
} }
} }
else else
@@ -262,9 +331,17 @@ public class VpnProfileDetailActivity extends Activity
mGateway.setError(getString(R.string.alert_text_no_input_gateway)); mGateway.setError(getString(R.string.alert_text_no_input_gateway));
valid = false; 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; valid = false;
} }
if (!mCheckAuto.isChecked() && mCertEntry == null) if (!mCheckAuto.isChecked() && mCertEntry == null)
@@ -285,10 +362,18 @@ public class VpnProfileDetailActivity extends Activity
String gateway = mGateway.getText().toString().trim(); String gateway = mGateway.getText().toString().trim();
mProfile.setName(name.isEmpty() ? gateway : name); mProfile.setName(name.isEmpty() ? gateway : name);
mProfile.setGateway(gateway); mProfile.setGateway(gateway);
mProfile.setUsername(mUsername.getText().toString().trim()); mProfile.setVpnType(mVpnType);
String password = mPassword.getText().toString().trim(); if (mVpnType.getRequiresUsernamePassword())
password = password.isEmpty() ? null : password; {
mProfile.setPassword(password); 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(); String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias();
mProfile.setCertificateAlias(certAlias); mProfile.setCertificateAlias(certAlias);
} }
@@ -300,18 +385,20 @@ public class VpnProfileDetailActivity extends Activity
*/ */
private void loadProfileData(Bundle savedInstanceState) private void loadProfileData(Bundle savedInstanceState)
{ {
String alias = null; String useralias = null, alias = null;
getActionBar().setTitle(R.string.add_profile); getActionBar().setTitle(R.string.add_profile);
if (mId != null) if (mId != null && mId != 0)
{ {
mProfile = mDataSource.getVpnProfile(mId); mProfile = mDataSource.getVpnProfile(mId);
if (mProfile != null) if (mProfile != null)
{ {
mName.setText(mProfile.getName()); mName.setText(mProfile.getName());
mGateway.setText(mProfile.getGateway()); mGateway.setText(mProfile.getGateway());
mVpnType = mProfile.getVpnType();
mUsername.setText(mProfile.getUsername()); mUsername.setText(mProfile.getUsername());
mPassword.setText(mProfile.getPassword()); mPassword.setText(mProfile.getPassword());
useralias = mProfile.getUserCertificateAlias();
alias = mProfile.getCertificateAlias(); alias = mProfile.getCertificateAlias();
getActionBar().setTitle(mProfile.getName()); 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); alias = savedInstanceState == null ? alias : savedInstanceState.getString(VpnProfileDataSource.KEY_CERTIFICATE);
mCheckAuto.setChecked(alias == null); mCheckAuto.setChecked(alias == null);
if (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();
}
}
} }
@@ -64,7 +64,25 @@ public class VpnProfileAdapter extends ArrayAdapter<VpnProfile>
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway); tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway);
tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway()); tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway());
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username); 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; return vpnProfileView;
} }
+2
View File
@@ -67,6 +67,8 @@ LOCAL_SRC_FILES += $(call add_plugin, pem)
LOCAL_SRC_FILES += $(call add_plugin, pkcs1) 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, pkcs11)
LOCAL_SRC_FILES += $(call add_plugin, pubkey) LOCAL_SRC_FILES += $(call add_plugin, pubkey)