From: Tobias Brunner Date: Tue, 28 Aug 2012 15:02:53 +0000 (+0200) Subject: android: Added JNI method to retrieve user certificate and private key X-Git-Tag: 5.0.1~152^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3aba33868b1f5574d4230aca15f2281149d6c326;p=thirdparty%2Fstrongswan.git android: Added JNI method to retrieve user certificate and private key To simplify things the private key, the user certificate and the CA certificates are all put into the same list. --- diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 57d118faf6..8d07dd5b6b 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -199,6 +199,33 @@ failed: return FALSE; } +/** + * Converts the given Java array of byte arrays (byte[][]) to a linked list + * of chunk_t objects. + */ +static linked_list_t *convert_array_of_byte_arrays(JNIEnv *env, + jobjectArray jarray) +{ + linked_list_t *list; + jsize i; + + list = linked_list_create(); + for (i = 0; i < (*env)->GetArrayLength(env, jarray); ++i) + { + chunk_t *chunk; + jbyteArray jbytearray; + + chunk = malloc_thing(chunk_t); + list->insert_last(list, chunk); + + jbytearray = (*env)->GetObjectArrayElement(env, jarray, i); + *chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray)); + (*env)->GetByteArrayRegion(env, jbytearray, 0, chunk->len, chunk->ptr); + (*env)->DeleteLocalRef(env, jbytearray); + } + return list; +} + METHOD(charonservice_t, get_trusted_certificates, linked_list_t*, private_charonservice_t *this) { @@ -206,7 +233,6 @@ METHOD(charonservice_t, get_trusted_certificates, linked_list_t*, jmethodID method_id; jobjectArray jcerts; linked_list_t *list; - jsize i; androidjni_attach_thread(&env); @@ -222,21 +248,39 @@ METHOD(charonservice_t, get_trusted_certificates, linked_list_t*, { goto failed; } - list = linked_list_create(); - for (i = 0; i < (*env)->GetArrayLength(env, jcerts); ++i) - { - chunk_t *ca_cert; - jbyteArray jcert; + list = convert_array_of_byte_arrays(env, jcerts); + androidjni_detach_thread(); + return list; + +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; - ca_cert = malloc_thing(chunk_t); - list->insert_last(list, ca_cert); + androidjni_attach_thread(&env); - jcert = (*env)->GetObjectArrayElement(env, jcerts, i); - *ca_cert = chunk_alloc((*env)->GetArrayLength(env, jcert)); - (*env)->GetByteArrayRegion(env, jcert, 0, ca_cert->len, ca_cert->ptr); - (*env)->DeleteLocalRef(env, jcert); + method_id = (*env)->GetMethodID(env, + android_charonvpnservice_class, + "getUserCertificate", "()[[B"); + if (!method_id) + { + goto failed; + } + jencodings = (*env)->CallObjectMethod(env, this->vpn_service, method_id, NULL); + if (!jencodings) + { + goto failed; } - (*env)->DeleteLocalRef(env, jcerts); + list = convert_array_of_byte_arrays(env, jencodings); androidjni_detach_thread(); return list; @@ -323,6 +367,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(), diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index 706eaa2205..507010badf 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -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 * diff --git a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java index 71fc046115..9b502e89aa 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -21,6 +21,7 @@ import java.io.File; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; +import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -42,6 +43,8 @@ import android.net.VpnService; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.security.KeyChain; +import android.security.KeyChainException; import android.util.Log; public class CharonVpnService extends VpnService implements Runnable @@ -54,6 +57,7 @@ public class CharonVpnService extends VpnService implements Runnable private Thread mConnectionHandler; private VpnProfile mCurrentProfile; private volatile String mCurrentCertificateAlias; + private volatile String mCurrentUserCertificateAlias; private VpnProfile mNextProfile; private volatile boolean mProfileUpdated; private volatile boolean mTerminate; @@ -203,6 +207,7 @@ public class CharonVpnService extends VpnService implements Runnable /* store this in a separate (volatile) variable to avoid * a possible deadlock during deinitialization */ mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias(); + mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias(); setProfile(mCurrentProfile); setError(ErrorState.NO_ERROR); @@ -421,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 encodings = new ArrayList(); + 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 *