android: Added JNI method to retrieve user certificate and private key
authorTobias Brunner <tobias@strongswan.org>
Tue, 28 Aug 2012 15:02:53 +0000 (17:02 +0200)
committerTobias Brunner <tobias@strongswan.org>
Fri, 31 Aug 2012 16:24:46 +0000 (18:24 +0200)
To simplify things the private key, the user certificate and the CA
certificates are all put into the same list.

src/frontends/android/jni/libandroidbridge/charonservice.c
src/frontends/android/jni/libandroidbridge/charonservice.h
src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java

index 57d118f..8d07dd5 100644 (file)
@@ -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(),
index 706eaa2..507010b 100644 (file)
@@ -86,6 +86,17 @@ struct charonservice_t {
        linked_list_t *(*get_trusted_certificates)(charonservice_t *this);
 
        /**
+        * Get the configured user certificate chain and private key via JNI
+        *
+        * The first item in the returned list is the private key, followed by the
+        * user certificate and any remaining elements of the certificate chain.
+        *
+        * @return                              list of DER encoded objects (as chunk_t*),
+        *                                              NULL on failure
+        */
+       linked_list_t *(*get_user_certificate)(charonservice_t *this);
+
+       /**
         * Get the current vpnservice_builder_t object
         *
         * @return                              VpnService.Builder instance
index 71fc046..9b502e8 100644 (file)
@@ -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);
@@ -422,6 +427,41 @@ public class CharonVpnService extends VpnService implements Runnable
        }
 
        /**
+        * Function called via JNI to get a list containing the DER encoded private key
+        * and DER encoded certificates of the user selected certificate chain (beginning
+        * with the user certificate).
+        *
+        * Since this method is called from a thread of charon's thread pool we are safe
+        * to call methods on KeyChain directly.
+        *
+        * @return list containing the private key and certificates (first element is the key)
+        * @throws InterruptedException
+        * @throws KeyChainException
+        * @throws CertificateEncodingException
+        */
+       private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException
+       {
+               ArrayList<byte[]> encodings = new ArrayList<byte[]>();
+               String alias = mCurrentUserCertificateAlias;
+               PrivateKey key = KeyChain.getPrivateKey(getApplicationContext(), alias);
+               if (key == null)
+               {
+                       return null;
+               }
+               encodings.add(key.getEncoded());
+               X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), alias);
+               if (chain == null || chain.length == 0)
+               {
+                       return null;
+               }
+               for (X509Certificate cert : chain)
+               {
+                       encodings.add(cert.getEncoded());
+               }
+               return encodings.toArray(new byte[encodings.size()][]);
+       }
+
+       /**
         * Initialization of charon, provided by libandroidbridge.so
         *
         * @param builder BuilderAdapter for this connection