Merge branch 'android-client-cert'
Introduces IKEv2 client certificate authentication for the Android App.
This commit is contained in:
+1
-1
@@ -17,7 +17,7 @@ include $(CLEAR_VARS)
|
||||
# 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)
|
||||
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
|
||||
|
||||
ifneq ($(strongswan_BUILD_SCEPCLIENT),)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,15 @@ struct private_android_creds_t {
|
||||
bool loaded;
|
||||
};
|
||||
|
||||
/**
|
||||
* Free allocated DER encoding
|
||||
*/
|
||||
static void free_encoding(chunk_t *chunk)
|
||||
{
|
||||
chunk_free(chunk);
|
||||
free(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load trusted certificates via charonservice (JNI).
|
||||
*/
|
||||
@@ -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**)¤t) == 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,
|
||||
},
|
||||
|
||||
@@ -46,6 +46,13 @@ struct android_creds_t {
|
||||
void (*add_username_password)(android_creds_t *this, char *username,
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -43,11 +43,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
|
||||
*/
|
||||
@@ -63,6 +73,11 @@ struct private_android_service_t {
|
||||
*/
|
||||
char *username;
|
||||
|
||||
/**
|
||||
* password
|
||||
*/
|
||||
char *password;
|
||||
|
||||
/**
|
||||
* 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 */
|
||||
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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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_ @}*/
|
||||
|
||||
@@ -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)
|
||||
list = convert_array_of_byte_arrays(env, jcerts);
|
||||
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;
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,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
|
||||
*
|
||||
|
||||
@@ -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_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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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="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…</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>
|
||||
|
||||
@@ -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="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…</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>
|
||||
|
||||
@@ -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="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…</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>
|
||||
|
||||
@@ -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 = certificate;
|
||||
this.mCertificate = alias;
|
||||
}
|
||||
|
||||
public String getUserCertificateAlias()
|
||||
{
|
||||
return mUserCertificate;
|
||||
}
|
||||
|
||||
public void setUserCertificateAlias(String alias)
|
||||
{
|
||||
this.mUserCertificate = alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.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());
|
||||
}
|
||||
@@ -420,6 +426,41 @@ public class CharonVpnService extends VpnService implements Runnable
|
||||
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
|
||||
*
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,32 +60,10 @@ 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)
|
||||
{
|
||||
@@ -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,36 +140,23 @@ 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.
|
||||
*/
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+219
-23
@@ -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());
|
||||
@@ -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
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user