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
|
# 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),)
|
||||||
|
|||||||
@@ -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**)¤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,
|
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…</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…</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…</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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+219
-23
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user