From c35d468fb1c75f432993d927b556629c4b761f84 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sun, 23 Sep 2012 08:58:37 +0200 Subject: android: Added a JNI backed private key implementation This is required because private keys are provided by an OpenSSL engine in Jelly Bean, which makes them inaccessible directly via getEncoding. --- .../android/jni/libandroidbridge/Android.mk | 1 + .../libandroidbridge/backend/android_private_key.c | 285 +++++++++++++++++++++ .../libandroidbridge/backend/android_private_key.h | 38 +++ 3 files changed, 324 insertions(+) create mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_private_key.c create mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_private_key.h diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index e1806f702..f47e39656 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -6,6 +6,7 @@ LOCAL_SRC_FILES := \ android_jni.c android_jni.h \ backend/android_attr.c backend/android_attr.h \ backend/android_creds.c backend/android_creds.h \ +backend/android_private_key.c backend/android_private_key.h \ backend/android_service.c backend/android_service.h \ charonservice.c charonservice.h \ kernel/android_ipsec.c kernel/android_ipsec.h \ diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_private_key.c b/src/frontends/android/jni/libandroidbridge/backend/android_private_key.c new file mode 100644 index 000000000..e8a78b5da --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_private_key.c @@ -0,0 +1,285 @@ +/* + * 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 . + * + * 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. + */ + +#include "android_private_key.h" + +#include "../android_jni.h" +#include + +typedef struct private_private_key_t private_private_key_t; + +/** + * Private data of a android_private_key_t object. + */ +struct private_private_key_t { + + /** + * Public interface + */ + private_key_t public; + + /** + * reference to the Java PrivateKey object + */ + jobject key; + + /** + * Java class used to build signatures + */ + jclass signature_class; + + /** + * public key that belongs to this private key + */ + public_key_t *pubkey; + + /** + * reference count + */ + refcount_t ref; +}; + +/** + * Converts the given Java byte array to a chunk + */ +static chunk_t chunk_from_byte_array(JNIEnv *env, jbyteArray jbytearray) +{ + chunk_t chunk; + + chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray)); + (*env)->GetByteArrayRegion(env, jbytearray, 0, chunk.len, chunk.ptr); + return chunk; +} + +/** + * Converts the given chunk to a Java byte array + */ +static jbyteArray byte_array_from_chunk(JNIEnv *env, chunk_t chunk) +{ + jbyteArray jbytearray; + + jbytearray = (*env)->NewByteArray(env, chunk.len); + (*env)->SetByteArrayRegion(env, jbytearray, 0, chunk.len, chunk.ptr); + return jbytearray; +} + +METHOD(private_key_t, sign, bool, + private_private_key_t *this, signature_scheme_t scheme, + chunk_t data, chunk_t *signature) +{ + JNIEnv *env; + jmethodID method_id; + const char *method; + jstring jmethod; + jobject jsignature; + jbyteArray jdata, jsigarray; + + switch (scheme) + { + case SIGN_RSA_EMSA_PKCS1_MD5: + method = "MD5withRSA"; + break; + case SIGN_RSA_EMSA_PKCS1_SHA1: + method = "SHA1withRSA"; + break; + case SIGN_RSA_EMSA_PKCS1_SHA224: + method = "SHA224withRSA"; + break; + case SIGN_RSA_EMSA_PKCS1_SHA256: + method = "SHA256withRSA"; + break; + case SIGN_RSA_EMSA_PKCS1_SHA384: + method = "SHA384withRSA"; + break; + case SIGN_RSA_EMSA_PKCS1_SHA512: + method = "SHA512withRSA"; + break; + default: + DBG1(DBG_LIB, "signature scheme %N not supported via JNI", + signature_scheme_names, scheme); + return FALSE; + } + + androidjni_attach_thread(&env); + /* we use java.security.Signature to create the signature without requiring + * access to the actual private key */ + method_id = (*env)->GetStaticMethodID(env, this->signature_class, + "getInstance", "(Ljava/lang/String;)Ljava/security/Signature;"); + if (!method_id) + { + goto failed; + } + jmethod = (*env)->NewStringUTF(env, method); + if (!jmethod) + { + goto failed; + } + jsignature = (*env)->CallStaticObjectMethod(env, this->signature_class, + method_id, jmethod); + if (!jsignature) + { + goto failed; + } + method_id = (*env)->GetMethodID(env, this->signature_class, "initSign", + "(Ljava/security/PrivateKey;)V"); + if (!method_id) + { + goto failed; + } + (*env)->CallVoidMethod(env, jsignature, method_id, this->key); + if (androidjni_exception_occurred(env)) + { + goto failed; + } + method_id = (*env)->GetMethodID(env, this->signature_class, "update", + "([B)V"); + if (!method_id) + { + goto failed; + } + jdata = byte_array_from_chunk(env, data); + (*env)->CallVoidMethod(env, jsignature, method_id, jdata); + if (androidjni_exception_occurred(env)) + { + goto failed; + } + method_id = (*env)->GetMethodID(env, this->signature_class, "sign", + "()[B"); + if (!method_id) + { + goto failed; + } + jsigarray = (*env)->CallObjectMethod(env, jsignature, method_id); + if (!jsigarray) + { + goto failed; + } + *signature = chunk_from_byte_array(env, jsigarray); + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "failed to build %N signature via JNI", + signature_scheme_names, scheme); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(private_key_t, get_type, key_type_t, + private_private_key_t *this) +{ + return KEY_RSA; +} + +METHOD(private_key_t, decrypt, bool, + private_private_key_t *this, encryption_scheme_t scheme, + chunk_t crypto, chunk_t *plain) +{ + DBG1(DBG_LIB, "private key decryption is currently not supported via JNI"); + return FALSE; +} + +METHOD(private_key_t, get_keysize, int, + private_private_key_t *this) +{ + return this->pubkey->get_keysize(this->pubkey); +} + +METHOD(private_key_t, get_public_key, public_key_t*, + private_private_key_t *this) +{ + return this->pubkey->get_ref(this->pubkey); +} + +METHOD(private_key_t, get_encoding, bool, + private_private_key_t *this, cred_encoding_type_t type, + chunk_t *encoding) +{ + return FALSE; +} + +METHOD(private_key_t, get_fingerprint, bool, + private_private_key_t *this, cred_encoding_type_t type, chunk_t *fp) +{ + return this->pubkey->get_fingerprint(this->pubkey, type, fp); +} + +METHOD(private_key_t, get_ref, private_key_t*, + private_private_key_t *this) +{ + ref_get(&this->ref); + return &this->public; +} + +METHOD(private_key_t, destroy, void, + private_private_key_t *this) +{ + if (ref_put(&this->ref)) + { + JNIEnv *env; + + androidjni_attach_thread(&env); + (*env)->DeleteGlobalRef(env, this->key); + (*env)->DeleteGlobalRef(env, this->signature_class); + androidjni_detach_thread(); + this->pubkey->destroy(this->pubkey); + free(this); + } +} + +/* + * See header + */ +private_key_t *android_private_key_create(jobject key, public_key_t *pubkey) +{ + JNIEnv *env; + private_private_key_t *this; + + INIT(this, + .public = { + .get_type = _get_type, + .sign = _sign, + .decrypt = _decrypt, + .get_keysize = _get_keysize, + .get_public_key = _get_public_key, + .belongs_to = private_key_belongs_to, + .equals = private_key_equals, + .get_fingerprint = _get_fingerprint, + .has_fingerprint = private_key_has_fingerprint, + .get_encoding = _get_encoding, + .get_ref = _get_ref, + .destroy = _destroy, + }, + .ref = 1, + .pubkey = pubkey, + ); + + if (!pubkey) + { + free(this); + return NULL; + } + + /* in ICS we could simply call getEncoded and use the PKCS#8/DER encoded + * private key, since JB that's not possible as there is no direct access + * to private keys anymore (as these could now be hardware backed) */ + androidjni_attach_thread(&env); + this->key = (*env)->NewGlobalRef(env, key); + this->signature_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env, + "java/security/Signature")); + androidjni_detach_thread(); + return &this->public; +} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_private_key.h b/src/frontends/android/jni/libandroidbridge/backend/android_private_key.h new file mode 100644 index 000000000..9848657de --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_private_key.h @@ -0,0 +1,38 @@ +/* + * 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 . + * + * 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. + */ + +/** + * @defgroup android_private_key android_private_key + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_PRIVATE_KEY_H_ +#define ANDROID_PRIVATE_KEY_H_ + +#include + +#include + +/** + * Create a JNI backed key, stored in the Android KeyChain + * + * @param key PrivateKey instance + * @param pubkey public key as extracted from the certificate (gets adopted) + * @return private_key_t instance + */ +private_key_t *android_private_key_create(jobject key, public_key_t *pubkey); + +#endif /** ANDROID_PRIVATE_KEY_H_ @}*/ + -- cgit v1.2.3