From 7569c7548bc2bbe7dbeb9a73a56098271e4704f1 Mon Sep 17 00:00:00 2001 From: Brian Pellin Date: Sun, 21 Nov 2010 15:38:33 -0600 Subject: [PATCH] Import native code changes from Mike Mohr. --- .gitignore | 1 + CONTRIBUTORS | 3 +- LICENSE | 25 +++ README | 4 +- jni/aes/.gitignore | 21 ++ jni/aes/Android.mk | 13 ++ jni/final_key/Android.mk | 7 +- jni/final_key/aes.c | 140 ------------- jni/final_key/final_key.c | 84 -------- jni/final_key/kpd_jni.c | 428 ++++++++++++++++++++++++++++++++++++++ jni/sha/.gitignore | 13 ++ jni/sha/Android.mk | 14 ++ jni_root/.gitignore | 2 + jni_root/Application.mk | 6 +- jni_root/build.patch | 302 --------------------------- jni_root/prep_build.sh | 12 +- 16 files changed, 535 insertions(+), 540 deletions(-) create mode 100644 jni/aes/.gitignore create mode 100644 jni/aes/Android.mk delete mode 100644 jni/final_key/aes.c delete mode 100644 jni/final_key/final_key.c create mode 100644 jni/final_key/kpd_jni.c create mode 100644 jni/sha/.gitignore create mode 100644 jni/sha/Android.mk delete mode 100644 jni_root/build.patch diff --git a/.gitignore b/.gitignore index b3691cc17..eb8ccedb8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build.properties local.properties bin gen +obj diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f570e0291..e4b51b18f 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3,6 +3,7 @@ Brian Pellin Achim Weimert Johan Berts - search patches +Mike Mohr - Better native code for aes and sha Tobias Selig - icon support Tolga Onbay - password generator @@ -13,4 +14,4 @@ Laurent, Norman Obry - French Maciej Bieniek - Polish Максим Сёмочкин - Russian MaWi - German -yslandro - Norwegian Nynorsk \ No newline at end of file +yslandro - Norwegian Nynorsk diff --git a/LICENSE b/LICENSE index 3da675b82..a574ebc7d 100644 --- a/LICENSE +++ b/LICENSE @@ -261,3 +261,28 @@ no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". --- + +Many files under jni/final_key/ are coverage by the following license: + + Copyright (c) 1998-2008, Brian Gladman, Worcester, UK. All rights reserved. + + LICENSE TERMS + + The redistribution and use of this software (with or without changes) + is allowed without the payment of fees or royalties provided that: + + 1. source code distributions include the above copyright notice, this + list of conditions and the following disclaimer; + + 2. binary distributions include the above copyright notice, this list + of conditions and the following disclaimer in their documentation; + + 3. the name of the copyright holder is not used to endorse products + built using this software without specific written permission. + + DISCLAIMER + + This software is provided 'as is' with no explicit or implied warranties + in respect of its properties, including, but not limited to, correctness + and/or fitness for purpose. + diff --git a/README b/README index b52082fae..16021bb43 100644 --- a/README +++ b/README @@ -5,6 +5,4 @@ Build instructions: c. Move the KeePassDroid git root to ${ndk-root}/apps/KeePassDroid/project d. cp -a ${ndk-root}/apps/KeePassDroid/project/jni_root ${ndk-root}/apps/KeePassDroid -2. Run prep-build.sh from ${ndk-root}/apps/KeePassDroid to download, unpack, and patch the openssl sources. - -3. At ${ndk-root} run 'make APP=KeePassDroid' to build the native sources. +2. At ${ndk-root} run 'make APP=KeePassDroid' to build the native sources. diff --git a/jni/aes/.gitignore b/jni/aes/.gitignore new file mode 100644 index 000000000..7c1d6a726 --- /dev/null +++ b/jni/aes/.gitignore @@ -0,0 +1,21 @@ +aes_amd64.asm +aescpp.h +aescrypt.c +aes.h +aeskey.c +aes_modes.c +aesopt.h +aestab.c +aestab.h +aes.txt +aes_via_ace.h +aes_x86_v1.asm +aes_x86_v2.asm +aesxam.c +brg_endian.h +brg_types.h +rfc3686.c +tablegen.c +vbaxam.doc +vb.txt +via_ace.txt diff --git a/jni/aes/Android.mk b/jni/aes/Android.mk new file mode 100644 index 000000000..e8d12daea --- /dev/null +++ b/jni/aes/Android.mk @@ -0,0 +1,13 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := aes + +LOCAL_SRC_FILES := \ + aescrypt.c \ + aeskey.c \ + aes_modes.c \ + aestab.c + +include $(BUILD_STATIC_LIBRARY) diff --git a/jni/final_key/Android.mk b/jni/final_key/Android.mk index 5f372097f..5c460e782 100644 --- a/jni/final_key/Android.mk +++ b/jni/final_key/Android.mk @@ -4,11 +4,12 @@ include $(CLEAR_VARS) LOCAL_MODULE := final-key -LOCAL_SRC_FILES := final_key.c aes.c +LOCAL_SRC_FILES := \ + kpd_jni.c -LOCAL_C_INCLUDES := $(LOCAL_PATH)/../openssl-0.9.8l/include +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../sha $(LOCAL_PATH)/../aes -LOCAL_STATIC_LIBRARIES := openssl-crypto +LOCAL_STATIC_LIBRARIES := aes sha LOCAL_LDLIBS := -llog diff --git a/jni/final_key/aes.c b/jni/final_key/aes.c deleted file mode 100644 index 332469824..000000000 --- a/jni/final_key/aes.c +++ /dev/null @@ -1,140 +0,0 @@ -#include -#include -#include -#include - -#include - -#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "KeePassDroidNative", __VA_ARGS__) -#define LOGD(...) -//#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "KeePassDroidNative", __VA_ARGS__) -#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , "KeePassDroidNative", __VA_ARGS__) -#define LOGW(...) __android_log_print(ANDROID_LOG_WARN , "KeePassDroidNative", __VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , "KeePassDroidNative", __VA_ARGS__) - -jlong Java_com_keepassdroid_crypto_NativeAESCipherSpi_nativeInit(JNIEnv *env, - jobject this, jboolean encrypt, jbyteArray key, jbyteArray iv, - jboolean padding) { - - LOGD("1"); - // Convert keys to c - jsize key_len = (*env)->GetArrayLength(env, key); - char *c_key = (char *) malloc(key_len); - (*env)->GetByteArrayRegion(env, key, 0, key_len, c_key); - LOGD("2: Keylen: %d", key_len); - - - // Covert iv to c - jsize iv_len = (*env)->GetArrayLength(env, iv); - char *c_iv = (char *) malloc(iv_len); - (*env)->GetByteArrayRegion(env, iv, 0, iv_len, c_iv); - LOGD("3: IvLen: %d", iv_len); - - EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *) malloc(sizeof(EVP_CIPHER_CTX)); - LOGD("3.5: %d", sizeof(EVP_CIPHER_CTX)); - - EVP_CIPHER_CTX_init(ctx); - EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), NULL, c_key, c_iv, encrypt); - - LOGD("4"); - if ( padding ) { - EVP_CIPHER_CTX_set_padding(ctx, 1); - } else { - EVP_CIPHER_CTX_set_padding(ctx, 0); - } - - LOGD("5"); - // Free allocated memory - free(c_iv); - free(c_key); - - LOGD("6: ctxPtr=%d",ctx); - - return (jlong) ctx; -} - -void Java_com_keepassdroid_crypto_NativeAESCipherSpi_nativeCleanup(JNIEnv *env, - jclass this, jlong ctxPtr) { - - LOGD("cleanup"); - EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *) ctxPtr; - - EVP_CIPHER_CTX_cleanup(ctx); - free(ctx); -} - -jint Java_com_keepassdroid_crypto_NativeAESCipherSpi_nativeUpdate(JNIEnv *env, - jobject this, jlong ctxPtr, jbyteArray input, jint inputOffset, - jint inputLen, jbyteArray output, jint outputOffset, jint outputSize) { - - - LOGD("InputSize: %d; InputOffset: %d, OutputOffset: %d, OutputSize: %d", inputLen, inputOffset, - outputOffset, outputSize); - if ( inputLen == 0 ) { - return 0; - } - - char *c_input = (char *) malloc(inputLen); - - (*env)->GetByteArrayRegion(env, input, inputOffset, inputLen, c_input); - - int outLen; - char *c_output; - - // Worst case is all full blocks with 1 byte on the left and right - //int max_update_size = (((inputLen - 2) / AES_BLOCK_SIZE) + 2) * AES_BLOCK_SIZE; - c_output = (char *) malloc(outputSize); - - EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *) ctxPtr; - LOGD("Pre: ctxPtr=%d", ctx); - EVP_CipherUpdate(ctx, c_output, &outLen, c_input, inputLen); - LOGD("Post"); - - /* output can differ on final - if ( outLen != ((int) outputSize) ) { - LOGD("Outsize differs: %d", outLen); - free(c_output); - free(c_input); - return -1; - } - */ - - LOGD("PreOut: outputSize=%d, outputOffset=%d, OutLen=%d", outputSize, outputOffset, outLen); - (*env)->SetByteArrayRegion(env, output, outputOffset, outLen, c_output); - - free(c_output); - free(c_input); - LOGD("PostOut"); - - - - return (jint) outLen; // I think jint should always be bigger than int - -} - -jint Java_com_keepassdroid_crypto_NativeAESCipherSpi_nativeDoFinal(JNIEnv *env, - jobject this, jlong ctxPtr, jbyteArray output, jint outputOffset, - jint outputSize) { - - LOGD("outputOffset=%d", outputOffset); - char *c_output = (char *) malloc(outputSize); - - int outLen; - EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *) ctxPtr; - EVP_CipherFinal_ex(ctx, c_output, &outLen); - - /* - if ( outLen != ((int) outputSize) ) { - free(c_output); - return -1; - } - */ - LOGD("Final: OutputLen=%d, outputOffset=%d", outLen, (int)outputOffset); - - (*env)->SetByteArrayRegion(env, output, outputOffset, outLen, c_output); - - free(c_output); - - return (jint) outLen; - -} diff --git a/jni/final_key/final_key.c b/jni/final_key/final_key.c deleted file mode 100644 index c47365741..000000000 --- a/jni/final_key/final_key.c +++ /dev/null @@ -1,84 +0,0 @@ -#include -#include -#include -#include - -#define BLOCK_SIZE AES_BLOCK_SIZE * 2 - -/* Prepare the final key */ -jbyteArray Java_com_keepassdroid_crypto_finalkey_NativeFinalKey_nativeTransformMasterKey(JNIEnv *env, jobject this, - jbyteArray seed, jbyteArray key, jint rounds) { - - char cSeed[BLOCK_SIZE]; - char key1[BLOCK_SIZE]; - char key2[BLOCK_SIZE]; - - // Verify length of key and seed - if ( (*env)->GetArrayLength(env, seed) != BLOCK_SIZE ) { - return NULL; - } - if ( (*env)->GetArrayLength(env, key) != BLOCK_SIZE ) { - return NULL; - } - - // TODO: Test to see if GetByteArrayElements is cheaper - (*env)->GetByteArrayRegion(env, seed, 0, BLOCK_SIZE, cSeed); - (*env)->GetByteArrayRegion(env, key, 0, BLOCK_SIZE, key1); - - EVP_CIPHER_CTX ctx; - - EVP_CIPHER_CTX_init(&ctx); - EVP_EncryptInit_ex(&ctx, EVP_aes_256_ecb(), NULL, cSeed, NULL); - - int c_len; // Not really needed, we always work at even block sizes here - int i; - char flip = 0; - for (i = 0; i < rounds; i++) { - // Toggle between arrays as source and destination - if ( flip ) { - EVP_EncryptUpdate(&ctx, key1, &c_len, key2, BLOCK_SIZE); - flip = 0; - } else { - EVP_EncryptUpdate(&ctx, key2, &c_len, key1, BLOCK_SIZE); - flip = 1; - } - } - - EVP_CIPHER_CTX_cleanup(&ctx); - - jbyteArray result = (*env)->NewByteArray(env, BLOCK_SIZE); - - - // Need to take the SHA1 digest - EVP_MD_CTX digestCtx; - EVP_MD_CTX_init(&digestCtx); - - EVP_DigestInit_ex(&digestCtx, EVP_sha256(), NULL); - - if ( flip ) { - EVP_DigestUpdate(&digestCtx, key2, BLOCK_SIZE); - EVP_DigestFinal_ex(&digestCtx, key1, NULL); - flip = 0; - } else { - EVP_DigestUpdate(&digestCtx, key1, BLOCK_SIZE); - EVP_DigestFinal_ex(&digestCtx, key2, NULL); - flip = 1; - } - - EVP_MD_CTX_cleanup(&digestCtx); - - if ( flip ) { - (*env)->SetByteArrayRegion(env, result, 0, BLOCK_SIZE, key2); - } else { - (*env)->SetByteArrayRegion(env, result, 0, BLOCK_SIZE, key1); - } - - return result; -} - -/* For testing purposes only */ -jbyteArray Java_com_keepassdroid_crypto_finalkey_NativeFinalKey_nativeReflect(JNIEnv *env, jclass this, - jbyteArray key) { - - return key; -} diff --git a/jni/final_key/kpd_jni.c b/jni/final_key/kpd_jni.c new file mode 100644 index 000000000..236d25705 --- /dev/null +++ b/jni/final_key/kpd_jni.c @@ -0,0 +1,428 @@ +/* + This is a JNI wrapper for AES & SHA source code on Android. + Copyright (C) 2010 Michael Mohr + + 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 3 of the License, or + (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include + +#include "aes.h" +#include "sha2.h" + +static JavaVM *cached_vm; +static jclass bad_arg, no_mem; + +typedef enum { + ENCRYPTION, + DECRYPTION, + FINALIZED +} edir_t; + +typedef struct _aes_encryption_state { + edir_t direction; + uint32_t cache_len; + uint8_t iv[16], cache[16]; + aes_encrypt_ctx ctx[1]; +} aes_encryption_state; + +typedef struct _aes_decryption_state { + edir_t direction; + uint32_t cache_len; + uint8_t iv[16], cache[16]; + aes_decrypt_ctx ctx[1]; +} aes_decryption_state; + +JNIEXPORT jint JNICALL JNI_OnLoad( JavaVM *vm, void *reserved ) { + JNIEnv *env; + jclass cls; + + cached_vm = vm; + if((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6)) + return JNI_ERR; + + cls = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); + if( cls == NULL ) + return JNI_ERR; + bad_arg = (*env)->NewWeakGlobalRef(env, cls); + if( bad_arg == NULL ) + return JNI_ERR; + + cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if( cls == NULL ) + return JNI_ERR; + no_mem = (*env)->NewWeakGlobalRef(env, cls); + if( no_mem == NULL ) + return JNI_ERR; + + aes_init(); + + return JNI_VERSION_1_6; +} + +// called on garbage collection +JNIEXPORT void JNICALL JNI_OnUnload( JavaVM *vm, void *reserved ) { + JNIEnv *env; + if((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6)) { + return; + } + (*env)->DeleteWeakGlobalRef(env, bad_arg); + (*env)->DeleteWeakGlobalRef(env, no_mem); + return; +} + +JNIEXPORT jlong JNICALL Java_com_keepassdroid_crypto_NativeAESCipherSpi_nEncryptInit(JNIEnv *env, jobject this, jbyteArray key, jbyteArray iv) { + unsigned char ckey[32]; + aes_encryption_state *state; + jint key_len = (*env)->GetArrayLength(env, key); + jint iv_len = (*env)->GetArrayLength(env, iv); + + if( ! ( key_len == 16 || key_len == 24 || key_len == 32 ) || iv_len != 16 ) { + (*env)->ThrowNew(env, bad_arg, "Invalid length of key or iv"); + return -1; + } + + state = (aes_encryption_state *)malloc(sizeof(aes_encryption_state)); + if( state == NULL ) { + (*env)->ThrowNew(env, no_mem, "Cannot allocate memory for the encryption state"); + return -1; + } + memset(state, 0, sizeof(aes_encryption_state)); + + (*env)->GetByteArrayRegion(env, key, (jint)0, key_len, (jbyte *)ckey); + + state->direction = ENCRYPTION; + (*env)->GetByteArrayRegion(env, iv, (jint)0, iv_len, (jbyte *)state->iv); + aes_encrypt_key(ckey, key_len, state->ctx); + + return (jlong)state; +} + +JNIEXPORT jlong JNICALL Java_com_keepassdroid_crypto_NativeAESCipherSpi_nDecryptInit(JNIEnv *env, jobject this, jbyteArray key, jbyteArray iv) { + unsigned char ckey[32]; + aes_decryption_state *state; + jint key_len = (*env)->GetArrayLength(env, key); + jint iv_len = (*env)->GetArrayLength(env, iv); + + if( ! ( key_len == 16 || key_len == 24 || key_len == 32 ) || iv_len != 16 ) { + (*env)->ThrowNew(env, bad_arg, "Invalid length of key or iv"); + return -1; + } + + state = (aes_decryption_state *)malloc(sizeof(aes_decryption_state)); + if( state == NULL ) { + (*env)->ThrowNew(env, no_mem, "Cannot allocate memory for the decryption state"); + return -1; + } + memset(state, 0, sizeof(aes_decryption_state)); + + (*env)->GetByteArrayRegion(env, key, (jint)0, key_len, (jbyte *)ckey); + + state->direction = DECRYPTION; + (*env)->GetByteArrayRegion(env, iv, (jint)0, iv_len, (jbyte *)state->iv); + aes_decrypt_key(ckey, key_len, state->ctx); + + return (jlong)state; +} + +JNIEXPORT void JNICALL Java_com_keepassdroid_crypto_NativeAESCipherSpi_nCleanup(JNIEnv *env, jclass this, jlong state) { + if( state == 0 || state == -1 ) return; + free((void *)state); +} + +/* + TODO: + ---Performance--- [low priority] + Align memory with posix_memalign() (does Android support this?) +*/ + +JNIEXPORT jint JNICALL Java_com_keepassdroid_crypto_NativeAESCipherSpi_nEncryptUpdate(JNIEnv *env, jobject this, + jlong state, jbyteArray input, jint inputOffset, jint inputLen, jbyteArray output, jint outputOffset, jint outputSize) { + int32_t outLen, trailing_bytes, input_plus_cache_len; + uint8_t *c_input, *c_output; + aes_encryption_state *c_state; + + // step 1: first, some housecleaning + if( !inputLen || state <= 0 ) { + (*env)->ThrowNew(env, bad_arg, "Invalid input length or state"); + return -1; + } + c_state = (aes_encryption_state *)state; + if( c_state->direction != ENCRYPTION ) { + (*env)->ThrowNew(env, bad_arg, "Decryption state passed to encryption function"); + return -1; + } + input_plus_cache_len = inputLen + c_state->cache_len; + if( input_plus_cache_len < 16 ) { + (*env)->GetByteArrayRegion(env, input, inputOffset, inputLen, (jbyte *)(c_state->cache + c_state->cache_len)); + c_state->cache_len = input_plus_cache_len; + return 0; + } + trailing_bytes = input_plus_cache_len & 15; // mask bottom 4 bits + outLen = (input_plus_cache_len - trailing_bytes); // output length is now aligned to a 16-byte boundary + if( outLen > outputSize ) { + (*env)->ThrowNew(env, bad_arg, "Output buffer does not have enough space"); + return -1; + } + + // step 2: allocate memory to hold input and output data + c_input = (uint8_t *)malloc(input_plus_cache_len); + if( c_input == NULL ) { + (*env)->ThrowNew(env, no_mem, "Unable to allocate heap space for encryption input"); + return -1; + } + c_output = (uint8_t *)malloc(outLen); + if( c_output == NULL ) { + free(c_input); + (*env)->ThrowNew(env, no_mem, "Unable to allocate heap space for encryption output"); + return -1; + } + + // step 3: copy data from Java and encrypt it + if( c_state->cache_len ) { + memcpy(c_input, c_state->cache, c_state->cache_len); + (*env)->GetByteArrayRegion(env, input, inputOffset, inputLen, (jbyte *)(c_input + c_state->cache_len)); + } else { + (*env)->GetByteArrayRegion(env, input, inputOffset, inputLen, (jbyte *)c_input); + } + if( aes_cbc_encrypt(c_input, c_output, outLen, c_state->iv, c_state->ctx) != EXIT_SUCCESS ) { + free(c_input); + free(c_output); + (*env)->ThrowNew(env, bad_arg, "Failed to encrypt input data"); // FIXME: get a better exception class for this... + return -1; + } + (*env)->SetByteArrayRegion(env, output, outputOffset, outLen, (jbyte *)c_output); + + // step 4: cleanup and return + if( trailing_bytes ) { + c_state->cache_len = trailing_bytes; // set new cache length + memcpy(c_state->cache, (c_input + outLen), trailing_bytes); // cache overflow bytes for next call + } else { + c_state->cache_len = 0; + } + + free(c_input); + free(c_output); + + return outLen; +} + +/* +nDecryptUpdate: decrypt a block of data. The input need not be a multiple of the AES block size. +Parameters: + state - a saved pointer obtained from a call to nDecryptInit() + input - an allocated Java byte[] object containing the input data + inputOffset - encrypted data will be decrypted starting at inputOffset bytes from the beginning of input + inputLen - a limit on how many bytes will be encrypted from input + output - analogous to input + outputOffset - analogous inputOffset + outputSize - a limit to how many bytes will be accepted for output +*/ +JNIEXPORT jint JNICALL Java_com_keepassdroid_crypto_NativeAESCipherSpi_nDecryptUpdate(JNIEnv *env, jobject this, + jlong state, jbyteArray input, jint inputOffset, jint inputLen, jbyteArray output, jint outputOffset, jint outputSize) { + int32_t outLen, trailing_bytes, input_plus_cache_len; + uint8_t *c_input, *c_output; + aes_decryption_state *c_state; + + // step 1: first, some housecleaning + if( !inputLen || state <= 0 ) { + (*env)->ThrowNew(env, bad_arg, "Invalid input length or state"); + return -1; + } + c_state = (aes_decryption_state *)state; + if( c_state->direction != DECRYPTION ) { + (*env)->ThrowNew(env, bad_arg, "Encryption state passed to decryption function"); + return -1; + } + input_plus_cache_len = inputLen + c_state->cache_len; + if( input_plus_cache_len < 16 ) { + (*env)->GetByteArrayRegion(env, input, inputOffset, inputLen, (jbyte *)(c_state->cache + c_state->cache_len)); + c_state->cache_len = input_plus_cache_len; + return 0; + } + trailing_bytes = input_plus_cache_len & 15; // mask bottom 4 bits + outLen = (input_plus_cache_len - trailing_bytes); // output length is now aligned to a 16-byte boundary + if( outLen > outputSize ) { + (*env)->ThrowNew(env, bad_arg, "Output buffer does not have enough space"); + return -1; + } + + // step 2: allocate memory to hold input and output data + c_input = (uint8_t *)malloc(input_plus_cache_len); + if( c_input == NULL ) { + (*env)->ThrowNew(env, no_mem, "Unable to allocate heap space for decryption input"); + return -1; + } + c_output = (uint8_t *)malloc(outLen); + if( c_output == NULL ) { + free(c_input); + (*env)->ThrowNew(env, no_mem, "Unable to allocate heap space for decryption output"); + return -1; + } + + // step 3: copy data from Java and decrypt it + if( c_state->cache_len ) { + memcpy(c_input, c_state->cache, c_state->cache_len); + (*env)->GetByteArrayRegion(env, input, inputOffset, inputLen, (jbyte *)(c_input + c_state->cache_len)); + } else { + (*env)->GetByteArrayRegion(env, input, inputOffset, inputLen, (jbyte *)c_input); + } + if( aes_cbc_decrypt(c_input, c_output, outLen, c_state->iv, c_state->ctx) != EXIT_SUCCESS ) { + free(c_input); + free(c_output); + (*env)->ThrowNew(env, bad_arg, "Failed to decrypt input data"); // FIXME: get a better exception class for this... + return -1; + } + (*env)->SetByteArrayRegion(env, output, outputOffset, outLen, (jbyte *)c_output); + + // step 4: cleanup and return + if( trailing_bytes ) { + c_state->cache_len = trailing_bytes; // set new cache length + memcpy(c_state->cache, (c_input + outLen), trailing_bytes); // cache overflow bytes for next call + } else { + c_state->cache_len = 0; + } + + free(c_input); + free(c_output); + + return outLen; +} + +/* +nEncryptFinal: encrypt any data remaining in the state's block cache and perform PKCS#5 padding +*/ +JNIEXPORT jint JNICALL Java_com_keepassdroid_crypto_NativeAESCipherSpi_nEncryptFinal(JNIEnv *env, jobject this, + jlong state, jboolean doPadding, jbyteArray output, jint outputOffset, jint outputSize) { + uint32_t pad; + uint8_t final_output[16] __attribute__ ((aligned (16))); + aes_encryption_state *c_state; + + if( outputSize < 16 || state <= 0 ) { + (*env)->ThrowNew(env, bad_arg, "Invalid state or outputSize too small"); + return -1; + } + c_state = (aes_encryption_state *)state; + if( c_state->direction != ENCRYPTION ) { + (*env)->ThrowNew(env, bad_arg, "Cannot finalize the passed state identifier"); + return -1; + } + + // allow fetching of remaining bytes from cache + if( !doPadding ) { + (*env)->SetByteArrayRegion(env, output, outputOffset, c_state->cache_len, (jbyte *)c_state->cache); + c_state->direction = FINALIZED; + return c_state->cache_len; + } + + if( c_state->cache_len ) { + pad = 16 - c_state->cache_len; + memset(c_state->cache + c_state->cache_len, pad, pad); + } else { + memset(c_state->cache, 0x10, 16); + } + if( aes_cbc_encrypt(c_state->cache, final_output, 16, c_state->iv, c_state->ctx) != EXIT_SUCCESS ) { + (*env)->ThrowNew(env, bad_arg, "Failed to encrypt final data block"); // FIXME: get a better exception class for this... + return -1; + } + (*env)->SetByteArrayRegion(env, output, outputOffset, 16, (jbyte *)final_output); + c_state->direction = FINALIZED; + return 16; +} + +JNIEXPORT jint JNICALL Java_com_keepassdroid_crypto_NativeAESCipherSpi_nDecryptFinal(JNIEnv *env, jobject this, + jlong state, jboolean doPadding, jbyteArray output, jint outputOffset, jint outputSize) { + aes_decryption_state *c_state; + + if( outputSize < 16 || state <= 0 ) { + (*env)->ThrowNew(env, bad_arg, "Invalid state or outputSize too small"); + return -1; + } + c_state = (aes_decryption_state *)state; + if( c_state->direction != DECRYPTION ) { + (*env)->ThrowNew(env, bad_arg, "Cannot finalize the passed state identifier"); + return -1; + } + + // allow fetching of remaining bytes from cache + if( !doPadding ) { + (*env)->SetByteArrayRegion(env, output, outputOffset, c_state->cache_len, (jbyte *)c_state->cache); + c_state->direction = FINALIZED; + return c_state->cache_len; + } + + // FIXME: is there anything else to do here? + c_state->direction = FINALIZED; + return 0; +} + +#define MASTER_KEY_SIZE 32 +JNIEXPORT jbyteArray JNICALL Java_com_keepassdroid_crypto_finalkey_NativeFinalKey_nTransformMasterKey(JNIEnv *env, jobject this, jbyteArray seed, jbyteArray key, jint rounds) { + int i, flip = 0; + jbyteArray result; + aes_encrypt_ctx e_ctx[1] __attribute__ ((aligned (16))); + sha256_ctx h_ctx[1] __attribute__ ((aligned (16))); + unsigned char c_seed[MASTER_KEY_SIZE] __attribute__ ((aligned (16))); + unsigned char key1[MASTER_KEY_SIZE] __attribute__ ((aligned (16))); + unsigned char key2[MASTER_KEY_SIZE] __attribute__ ((aligned (16))); + + // step 1: housekeeping - sanity checks and fetch data from the JVM + if( (*env)->GetArrayLength(env, seed) != MASTER_KEY_SIZE ) { + (*env)->ThrowNew(env, bad_arg, "TransformMasterKey: the seed is not the correct size"); + return NULL; + } + if( (*env)->GetArrayLength(env, key) != MASTER_KEY_SIZE ) { + (*env)->ThrowNew(env, bad_arg, "TransformMasterKey: the key is not the correct size"); + return NULL; + } + (*env)->GetByteArrayRegion(env, seed, 0, MASTER_KEY_SIZE, (jbyte *)c_seed); + (*env)->GetByteArrayRegion(env, key, 0, MASTER_KEY_SIZE, (jbyte *)key1); + + // step 2: encrypt the hash "rounds" (default: 6000) times + aes_encrypt_key(c_seed, MASTER_KEY_SIZE, e_ctx); + for (i = 0; i < rounds; i++) { + if ( flip ) { + aes_ecb_encrypt(key2, key1, MASTER_KEY_SIZE, e_ctx); + flip = 0; + } else { + aes_ecb_encrypt(key1, key2, MASTER_KEY_SIZE, e_ctx); + flip = 1; + } // if + } // for + + // step 3: final SHA256 hash + sha256_begin(h_ctx); + if( flip ) { + sha256_hash(key2, MASTER_KEY_SIZE, h_ctx); + sha256_end(key1, h_ctx); + flip = 0; + } else { + sha256_hash(key1, MASTER_KEY_SIZE, h_ctx); + sha256_end(key2, h_ctx); + flip = 1; + } + + // step 4: send the hash into the JVM + result = (*env)->NewByteArray(env, MASTER_KEY_SIZE); + if( flip ) + (*env)->SetByteArrayRegion(env, result, 0, MASTER_KEY_SIZE, (jbyte *)key2); + else + (*env)->SetByteArrayRegion(env, result, 0, MASTER_KEY_SIZE, (jbyte *)key1); + return result; +} +#undef MASTER_KEY_SIZE diff --git a/jni/sha/.gitignore b/jni/sha/.gitignore new file mode 100644 index 000000000..18596cbc8 --- /dev/null +++ b/jni/sha/.gitignore @@ -0,0 +1,13 @@ +brg_endian.h +brg_types.h +hmac.c +hmac.h +pwd2key.c +pwd2key.h +sha1b.c +sha1.c +sha1.h +sha2b.c +sha2.c +sha2.h +shasum.c diff --git a/jni/sha/Android.mk b/jni/sha/Android.mk new file mode 100644 index 000000000..bfdad4e5c --- /dev/null +++ b/jni/sha/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := sha + +LOCAL_SRC_FILES := \ + sha1.c \ + sha2.c \ + hmac.c + +LOCAL_CFLAGS := -DUSE_SHA256 + +include $(BUILD_STATIC_LIBRARY) diff --git a/jni_root/.gitignore b/jni_root/.gitignore index 947e927e0..64e652127 100644 --- a/jni_root/.gitignore +++ b/jni_root/.gitignore @@ -1,2 +1,4 @@ openssl-0.9.8l.tar.gz project +aes-src-29-04-09.zip +sha2-07-01-07.zip diff --git a/jni_root/Application.mk b/jni_root/Application.mk index 9e2f2d73b..c96bd9028 100644 --- a/jni_root/Application.mk +++ b/jni_root/Application.mk @@ -1,2 +1,4 @@ -APP_PROJECT_PATH := $(call my-dir)/project -APP_MODULES := openssl-crypto final-key +APP_PROJECT_PATH := $(call my-dir) +APP_MODULES := aes sha final-key +APP_OPTIM := release +APP_ABI := armeabi armeabi-v7a diff --git a/jni_root/build.patch b/jni_root/build.patch deleted file mode 100644 index 0c46c00ad..000000000 --- a/jni_root/build.patch +++ /dev/null @@ -1,302 +0,0 @@ -diff -Nru openssl-0.9.8l.orig/Android.mk openssl-0.9.8l/Android.mk ---- openssl-0.9.8l.orig/Android.mk 1969-12-31 18:00:00.000000000 -0600 -+++ openssl-0.9.8l/Android.mk 2009-12-02 19:19:38.458446809 -0600 -@@ -0,0 +1,3 @@ -+# Recursively sources all Android.mk files in subdirs: -+include $(call all-subdir-makefiles) -+ -diff -Nru openssl-0.9.8l.orig/crypto/Android.mk openssl-0.9.8l/crypto/Android.mk ---- openssl-0.9.8l.orig/crypto/Android.mk 1969-12-31 18:00:00.000000000 -0600 -+++ openssl-0.9.8l/crypto/Android.mk 2009-12-01 21:02:53.490604631 -0600 -@@ -0,0 +1,291 @@ -+LOCAL_PATH := $(call my-dir) -+ -+include $(CLEAR_VARS) -+ -+LOCAL_MODULE := openssl-crypto -+ -+LOCAL_SRC_FILES := \ -+ cryptlib.c \ -+ dyn_lck.c \ -+ mem_clr.c \ -+ mem.c \ -+ mem_dbg.c \ -+ cversion.c \ -+ ex_data.c \ -+ tmdiff.c \ -+ cpt_err.c \ -+ ebcdic.c \ -+ uid.c \ -+ o_time.c \ -+ o_str.c \ -+ o_dir.c \ -+ o_init.c \ -+ fips_err.c \ -+ aes/aes_misc.c \ -+ aes/aes_ecb.c \ -+ aes/aes_cfb.c \ -+ aes/aes_ofb.c \ -+ aes/aes_ctr.c \ -+ aes/aes_ige.c \ -+ aes/aes_wrap.c \ -+ aes/aes_core.c \ -+ aes/aes_cbc.c \ -+ evp/encode.c \ -+ evp/digest.c \ -+ evp/dig_eng.c \ -+ evp/evp_enc.c \ -+ evp/evp_key.c \ -+ evp/evp_acnf.c \ -+ evp/evp_cnf.c \ -+ evp/e_des.c \ -+ evp/e_bf.c \ -+ evp/e_idea.c \ -+ evp/e_des3.c \ -+ evp/e_camellia.c \ -+ evp/e_rc4.c \ -+ evp/e_aes.c \ -+ evp/names.c \ -+ evp/e_seed.c \ -+ evp/e_xcbc_d.c \ -+ evp/e_rc2.c \ -+ evp/e_cast.c \ -+ evp/e_rc5.c \ -+ evp/enc_min.c \ -+ evp/m_null.c \ -+ evp/m_md2.c \ -+ evp/m_md4.c \ -+ evp/m_md5.c \ -+ evp/m_sha.c \ -+ evp/m_sha1.c \ -+ evp/m_dss.c \ -+ evp/m_dss1.c \ -+ evp/m_mdc2.c \ -+ evp/m_ripemd.c \ -+ evp/m_ecdsa.c \ -+ evp/p_open.c \ -+ evp/p_seal.c \ -+ evp/p_sign.c \ -+ evp/p_verify.c \ -+ evp/p_lib.c \ -+ evp/p_enc.c \ -+ evp/p_dec.c \ -+ evp/bio_md.c \ -+ evp/bio_b64.c \ -+ evp/bio_enc.c \ -+ evp/evp_err.c \ -+ evp/e_null.c \ -+ evp/c_all.c \ -+ evp/c_allc.c \ -+ evp/c_alld.c \ -+ evp/evp_lib.c \ -+ evp/bio_ok.c \ -+ evp/evp_pkey.c \ -+ evp/evp_pbe.c \ -+ evp/p5_crpt.c \ -+ evp/p5_crpt2.c \ -+ evp/e_old.c \ -+ engine/eng_err.c \ -+ engine/eng_lib.c \ -+ engine/eng_list.c \ -+ engine/eng_init.c \ -+ engine/eng_ctrl.c \ -+ engine/eng_table.c \ -+ engine/eng_pkey.c \ -+ engine/eng_fat.c \ -+ engine/eng_all.c \ -+ engine/tb_rsa.c \ -+ engine/tb_dsa.c \ -+ engine/tb_ecdsa.c \ -+ engine/tb_dh.c \ -+ engine/tb_ecdh.c \ -+ engine/tb_rand.c \ -+ engine/tb_store.c \ -+ engine/tb_cipher.c \ -+ engine/tb_digest.c \ -+ engine/eng_openssl.c \ -+ engine/eng_cnf.c \ -+ engine/eng_dyn.c \ -+ engine/eng_cryptodev.c \ -+ engine/eng_padlock.c \ -+ err/err.c \ -+ err/err_def.c \ -+ err/err_all.c \ -+ err/err_prn.c \ -+ err/err_str.c \ -+ err/err_bio.c \ -+ stack/stack.c \ -+ lhash/lhash.c \ -+ lhash/lh_stats.c \ -+ buffer/buffer.c \ -+ buffer/buf_str.c \ -+ buffer/buf_err.c \ -+ rand/md_rand.c \ -+ rand/randfile.c \ -+ rand/rand_lib.c \ -+ rand/rand_eng.c \ -+ rand/rand_err.c \ -+ rand/rand_egd.c \ -+ rand/rand_win.c \ -+ rand/rand_unix.c \ -+ rand/rand_os2.c \ -+ rand/rand_nw.c \ -+ bio/bio_lib.c \ -+ bio/bio_cb.c \ -+ bio/bio_err.c \ -+ bio/bss_mem.c \ -+ bio/bss_null.c \ -+ bio/bss_fd.c \ -+ bio/bss_file.c \ -+ bio/bss_sock.c \ -+ bio/bss_conn.c \ -+ bio/bf_null.c \ -+ bio/bf_buff.c \ -+ bio/b_print.c \ -+ bio/b_dump.c \ -+ bio/b_sock.c \ -+ bio/bss_acpt.c \ -+ bio/bf_nbio.c \ -+ bio/bss_log.c \ -+ bio/bss_bio.c \ -+ bio/bss_dgram.c \ -+ objects/o_names.c \ -+ objects/obj_dat.c \ -+ objects/obj_lib.c \ -+ objects/obj_err.c \ -+ sha/sha_dgst.c \ -+ sha/sha1dgst.c \ -+ sha/sha_one.c \ -+ sha/sha1_one.c \ -+ sha/sha256.c \ -+ sha/sha512.c \ -+ rsa/rsa_eay.c \ -+ rsa/rsa_gen.c \ -+ rsa/rsa_lib.c \ -+ rsa/rsa_sign.c \ -+ rsa/rsa_saos.c \ -+ rsa/rsa_err.c \ -+ rsa/rsa_pk1.c \ -+ rsa/rsa_ssl.c \ -+ rsa/rsa_none.c \ -+ rsa/rsa_oaep.c \ -+ rsa/rsa_chk.c \ -+ rsa/rsa_null.c \ -+ rsa/rsa_pss.c \ -+ rsa/rsa_x931.c \ -+ rsa/rsa_x931g.c \ -+ rsa/rsa_asn1.c \ -+ rsa/rsa_depr.c \ -+ rsa/rsa_eng.c \ -+ bn/bn_add.c \ -+ bn/bn_div.c \ -+ bn/bn_exp.c \ -+ bn/bn_lib.c \ -+ bn/bn_ctx.c \ -+ bn/bn_mul.c \ -+ bn/bn_mod.c \ -+ bn/bn_print.c \ -+ bn/bn_rand.c \ -+ bn/bn_shift.c \ -+ bn/bn_word.c \ -+ bn/bn_blind.c \ -+ bn/bn_kron.c \ -+ bn/bn_sqrt.c \ -+ bn/bn_gcd.c \ -+ bn/bn_prime.c \ -+ bn/bn_err.c \ -+ bn/bn_sqr.c \ -+ bn/bn_asm.c \ -+ bn/bn_recp.c \ -+ bn/bn_mont.c \ -+ bn/bn_mpi.c \ -+ bn/bn_exp2.c \ -+ bn/bn_gf2m.c \ -+ bn/bn_nist.c \ -+ bn/bn_depr.c \ -+ bn/bn_x931p.c \ -+ bn/bn_const.c \ -+ bn/bn_opt.c \ -+ asn1/a_object.c \ -+ asn1/a_bitstr.c \ -+ asn1/a_utctm.c \ -+ asn1/a_gentm.c \ -+ asn1/a_time.c \ -+ asn1/a_int.c \ -+ asn1/a_octet.c \ -+ asn1/a_print.c \ -+ asn1/a_type.c \ -+ asn1/a_set.c \ -+ asn1/a_dup.c \ -+ asn1/a_d2i_fp.c \ -+ asn1/a_i2d_fp.c \ -+ asn1/a_enum.c \ -+ asn1/a_utf8.c \ -+ asn1/a_sign.c \ -+ asn1/a_digest.c \ -+ asn1/a_verify.c \ -+ asn1/a_mbstr.c \ -+ asn1/a_strex.c \ -+ asn1/x_algor.c \ -+ asn1/x_val.c \ -+ asn1/x_pubkey.c \ -+ asn1/x_sig.c \ -+ asn1/x_req.c \ -+ asn1/x_attrib.c \ -+ asn1/x_bignum.c \ -+ asn1/x_long.c \ -+ asn1/x_name.c \ -+ asn1/x_x509.c \ -+ asn1/x_x509a.c \ -+ asn1/x_crl.c \ -+ asn1/x_info.c \ -+ asn1/x_spki.c \ -+ asn1/nsseq.c \ -+ asn1/d2i_pu.c \ -+ asn1/d2i_pr.c \ -+ asn1/i2d_pu.c \ -+ asn1/i2d_pr.c \ -+ asn1/t_req.c \ -+ asn1/t_x509.c \ -+ asn1/t_x509a.c \ -+ asn1/t_crl.c \ -+ asn1/t_pkey.c \ -+ asn1/t_spki.c \ -+ asn1/t_bitst.c \ -+ asn1/tasn_new.c \ -+ asn1/tasn_fre.c \ -+ asn1/tasn_enc.c \ -+ asn1/tasn_dec.c \ -+ asn1/tasn_utl.c \ -+ asn1/tasn_typ.c \ -+ asn1/f_int.c \ -+ asn1/f_string.c \ -+ asn1/n_pkey.c \ -+ asn1/f_enum.c \ -+ asn1/a_hdr.c \ -+ asn1/x_pkey.c \ -+ asn1/a_bool.c \ -+ asn1/x_exten.c \ -+ asn1/asn_mime.c \ -+ asn1/asn1_gen.c \ -+ asn1/asn1_par.c \ -+ asn1/asn1_lib.c \ -+ asn1/asn1_err.c \ -+ asn1/a_meth.c \ -+ asn1/a_bytes.c \ -+ asn1/a_strnid.c \ -+ asn1/evp_asn1.c \ -+ asn1/asn_pack.c \ -+ asn1/p5_pbe.c \ -+ asn1/p5_pbev2.c \ -+ asn1/p8_pkey.c \ -+ asn1/asn_moid.c \ -+ -+ -+ -+ -+ -+LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. $(LOCAL_PATH)/../include -+ -+LOCAL_CFLAGS := -DNO_WINDOWS_BRAINDEATH -+ -+include $(BUILD_STATIC_LIBRARY) diff --git a/jni_root/prep_build.sh b/jni_root/prep_build.sh index 0b6ba8d36..07b4eb842 100644 --- a/jni_root/prep_build.sh +++ b/jni_root/prep_build.sh @@ -1,10 +1,12 @@ #!/bin/sh - -FILE="openssl-0.9.8l.tar.gz" +http://gladman.plushost.co.uk/oldsite/AES/ +AES_FILE="aes-src-29-04-09.zip" +SHA_FILE="sha2-07-01-07.zip" EXTRACT_PATH=project/jni/ PATCH=build.patch ln -s ../ project -curl http://www.openssl.org/source/$FILE > $FILE -tar xzf $FILE -C $EXTRACT_PATH -patch -p1 -d $EXTRACT_PATH/openssl-0.9.8l < $PATCH +curl http://gladman.plushost.co.uk/oldsite/AES/$AES_FILE > $AES_FILE +unzip $AES_FILE -d $EXTRACT_PATH/aes +curl http://gladman.plushost.co.uk/oldsite/cryptography_technology/sha/$SHA_FILE > $SHA_FILE +unzip $SHA_FILE -d $EXTRACT_PATH/sha