From efc7466c67927a1a2dab984d37b19e6ce8e29f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20Guimar=C3=A3es?= Date: Fri, 3 Mar 2017 16:24:50 -0700 Subject: [PATCH] adds aes wrappers --- jni/include/com_wolfssl_wolfcrypt_Aes.h | 24 +++ jni/jni_aes.c | 117 ++++++++++- src/java/com/wolfssl/wolfcrypt/Aes.java | 86 +++++++- src/test/com/wolfssl/wolfcrypt/AesTest.java | 209 ++++++++++++++++++++ 4 files changed, 428 insertions(+), 8 deletions(-) diff --git a/jni/include/com_wolfssl_wolfcrypt_Aes.h b/jni/include/com_wolfssl_wolfcrypt_Aes.h index 3d2d87d..f6e1e38 100644 --- a/jni/include/com_wolfssl_wolfcrypt_Aes.h +++ b/jni/include/com_wolfssl_wolfcrypt_Aes.h @@ -29,6 +29,30 @@ extern "C" { JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_Aes_mallocNativeStruct (JNIEnv *, jobject); +/* + * Class: com_wolfssl_wolfcrypt_Aes + * Method: wc_AesSetKey + * Signature: ([B[BI)V + */ +JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Aes_wc_1AesSetKey + (JNIEnv *, jobject, jbyteArray, jbyteArray, jint); + +/* + * Class: com_wolfssl_wolfcrypt_Aes + * Method: native_update + * Signature: (I[BII[BI)I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Aes_native_1update__I_3BII_3BI + (JNIEnv *, jobject, jint, jbyteArray, jint, jint, jbyteArray, jint); + +/* + * Class: com_wolfssl_wolfcrypt_Aes + * Method: native_update + * Signature: (ILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;)I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Aes_native_1update__ILjava_nio_ByteBuffer_2IILjava_nio_ByteBuffer_2 + (JNIEnv *, jobject, jint, jobject, jint, jint, jobject); + #ifdef __cplusplus } #endif diff --git a/jni/jni_aes.c b/jni/jni_aes.c index de0d2c7..241ad61 100644 --- a/jni/jni_aes.c +++ b/jni/jni_aes.c @@ -25,6 +25,7 @@ #include #include +#include #include /* #define WOLFCRYPT_JNI_DEBUG_ON */ @@ -35,18 +36,122 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_Aes_mallocNativeStruct( { jlong ret = 0; -#ifdef NO_AES - throwNotCompiledInException(env); -#else - +#ifndef NO_AES ret = (jlong) XMALLOC(sizeof(Aes), NULL, DYNAMIC_TYPE_TMP_BUFFER); if (!ret) throwOutOfMemoryException(env, "Failed to allocate Aes object"); - LogStr("new Aes() = %p\n", ret); - + LogStr("new Aes() = %p\n", (void*)ret); +#else + throwNotCompiledInException(env); #endif return ret; } + +JNIEXPORT void JNICALL +Java_com_wolfssl_wolfcrypt_Aes_wc_1AesSetKey( + JNIEnv* env, jobject this, jbyteArray key_object, jbyteArray iv_object, + jint opmode) +{ +#ifndef NO_AES + int ret = 0; + Aes* aes = (Aes*) getNativeStruct(env, this); + byte* key = getByteArray(env, key_object); + byte* iv = getByteArray(env, iv_object); + word32 keySz = getByteArrayLength(env, key_object); + + ret = wc_AesSetKey(aes, key, keySz, iv, opmode); + if (ret != 0) + throwWolfCryptExceptionFromError(env, ret); + + LogStr("wc_AesSetKey(aes=%p, key, iv, opmode) = %d\n", aes, ret); +#else + throwNotCompiledInException(env); +#endif +} + +JNIEXPORT jint JNICALL +Java_com_wolfssl_wolfcrypt_Aes_native_1update__I_3BII_3BI( + JNIEnv* env, jobject this, jint opmode, + jbyteArray input_object, jint offset, jint length, + jbyteArray output_object, jint outputOffset) +{ +#ifndef NO_AES + int ret = 0; + Aes* aes = (Aes*) getNativeStruct(env, this); + byte* input = getByteArray(env, input_object); + byte* output = getByteArray(env, output_object); + + if (opmode == AES_ENCRYPTION) { + ret = wc_AesCbcEncrypt(aes, output+outputOffset, input+offset, length); + LogStr("wc_AesCbcEncrypt(aes=%p, out, in, inSz) = %d\n", aes, ret); + } + else { + ret = wc_AesCbcDecrypt(aes, output+outputOffset, input+offset, length); + LogStr("wc_AesCbcDecrypt(aes=%p, out, in, inSz) = %d\n", aes, ret); + } + + releaseByteArray(env, output_object, output, ret); + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + ret = 0; /* 0 bytes stored in output */ + } + else { + ret = length; + } + + LogStr("input[%u]: [%p]\n", (word32)length, input + offset); + LogHex((byte*) input + offset, length); + LogStr("output[%u]: [%p]\n", (word32)length, output + outputOffset); + LogHex((byte*) output + outputOffset, length); +#else + throwNotCompiledInException(env); +#endif + + return ret; +} + +JNIEXPORT jint JNICALL +Java_com_wolfssl_wolfcrypt_Aes_native_1update__ILjava_nio_ByteBuffer_2IILjava_nio_ByteBuffer_2( + JNIEnv* env, jobject this, jint opmode, + jobject input_object, jint offset, jint length, + jobject output_object) +{ + int ret = 0; + +#ifndef NO_AES + Aes* aes = (Aes*) getNativeStruct(env, this); + byte* input = getDirectBufferAddress(env, input_object); + byte* output = getDirectBufferAddress(env, output_object); + + if (opmode == AES_ENCRYPTION) { + ret = wc_AesCbcEncrypt(aes, output, input + offset, length); + LogStr("wc_AesCbcEncrypt(aes=%p, out, in, inSz) = %d\n", aes, ret); + } + else { + ret = wc_AesCbcDecrypt(aes, output, input + offset, length); + LogStr("wc_AesCbcDecrypt(aes=%p, out, in, inSz) = %d\n", aes, ret); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + ret = 0; /* 0 bytes stored in output */ + } + else { + ret = length; + } + + LogStr("input[%u]: [%p]\n", (word32)length, input + offset); + LogHex((byte*) input + offset, length); + LogStr("output[%u]: [%p]\n", (word32)length, output); + LogHex((byte*) output, length); +#else + throwNotCompiledInException(env); +#endif + + return ret; +} + diff --git a/src/java/com/wolfssl/wolfcrypt/Aes.java b/src/java/com/wolfssl/wolfcrypt/Aes.java index 6fda4c5..8b59e09 100644 --- a/src/java/com/wolfssl/wolfcrypt/Aes.java +++ b/src/java/com/wolfssl/wolfcrypt/Aes.java @@ -21,11 +21,15 @@ package com.wolfssl.wolfcrypt; +import java.nio.ByteBuffer; + +import javax.crypto.ShortBufferException; + /** * Wrapper for the native WolfCrypt Aes implementation. * * @author Moisés Guimarães - * @version 1.0, February 2015 + * @version 2.0, March 2017 */ public class Aes extends NativeStruct { @@ -36,5 +40,83 @@ public class Aes extends NativeStruct { public static final int ENCRYPT_MODE = 0; public static final int DECRYPT_MODE = 1; + private WolfCryptState state = WolfCryptState.UNINITIALIZED; + + private int opmode; + protected native long mallocNativeStruct() throws OutOfMemoryError; -} + + private native void wc_AesSetKey(byte[] key, byte[] iv, int opmode); + + private native int native_update(int opmode, byte[] input, int offset, + int length, byte[] output, int outputOffset); + + private native int native_update(int opmode, ByteBuffer plain, int offset, + int length, ByteBuffer cipher); + + public Aes() { + } + + public Aes(byte[] key, byte[] iv, int opmode) { + setKey(key, iv, opmode); + } + + public void setKey(byte[] key, byte[] iv, int opmode) { + wc_AesSetKey(key, iv, opmode); + + this.opmode = opmode; + state = WolfCryptState.READY; + } + + public byte[] update(byte[] input, int offset, int length) { + byte[] output; + + if (state == WolfCryptState.READY) { + output = new byte[input.length]; + + native_update(opmode, input, offset, length, output, 0); + } else { + throw new IllegalStateException( + "No available key to perform the opperation."); + } + + return output; + } + + public int update(byte[] input, int offset, int length, byte[] output, + int outputOffset) throws ShortBufferException { + if (state == WolfCryptState.READY) { + if (outputOffset + length > output.length) + throw new ShortBufferException( + "output buffer is too small to hold the result."); + + return native_update(opmode, input, offset, length, output, + outputOffset); + } else { + throw new IllegalStateException( + "No available key to perform the opperation."); + } + } + + public int update(ByteBuffer input, ByteBuffer output) + throws ShortBufferException { + int ret = 0; + + if (state == WolfCryptState.READY) { + if (output.remaining() < input.remaining()) + throw new ShortBufferException( + "output buffer is too small to hold the result."); + + ret = native_update(opmode, input, input.position(), + input.remaining(), output); + + output.position(output.position() + input.remaining()); + input.position(input.position() + input.remaining()); + } else { + throw new IllegalStateException( + "No available key to perform the opperation."); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/test/com/wolfssl/wolfcrypt/AesTest.java b/src/test/com/wolfssl/wolfcrypt/AesTest.java index b76daf5..19d5b4f 100644 --- a/src/test/com/wolfssl/wolfcrypt/AesTest.java +++ b/src/test/com/wolfssl/wolfcrypt/AesTest.java @@ -23,15 +23,224 @@ package com.wolfssl.wolfcrypt; import static org.junit.Assert.*; +import java.nio.ByteBuffer; + +import javax.crypto.ShortBufferException; + +import org.junit.Assume; +import org.junit.BeforeClass; import org.junit.Test; +import org.junit.internal.runners.statements.Fail; import com.wolfssl.wolfcrypt.Aes; public class AesTest { + @BeforeClass + public static void checkAvailability() { + try { + new Aes(); + } catch (WolfCryptException e) { + if (e.getError() == WolfCryptError.NOT_COMPILED_IN) + System.out.println("Aes test skipped: " + e.getError()); + Assume.assumeNoException(e); + } + } + @Test public void constructorShouldInitializeNativeStruct() { assertNotEquals(NativeStruct.NULL, new Aes().getNativeStruct()); } + @Test(expected=ShortBufferException.class) + public void updateShouldMatchUsingByteByffer() throws ShortBufferException { + String[] keys = new String[] { + "2b7e151628aed2a6abf7158809cf4f3c", + "2b7e151628aed2a6abf7158809cf4f3c", + "2b7e151628aed2a6abf7158809cf4f3c", + "2b7e151628aed2a6abf7158809cf4f3c", + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", }; + String[] ivs = new String[] { + "000102030405060708090A0B0C0D0E0F", + "7649ABAC8119B246CEE98E9B12E9197D", + "5086CB9B507219EE95DB113A917678B2", + "73BED6B8E3C1743B7116E69E22229516", + "000102030405060708090A0B0C0D0E0F", + "4F021DB243BC633D7178183A9FA071E8", + "B4D9ADA9AD7DEDF4E5E738763F69145A", + "571B242012FB7AE07FA9BAAC3DF102E0", + "000102030405060708090A0B0C0D0E0F", + "F58C4C04D6E5F1BA779EABFB5F7BFBD6", + "9CFC4E967EDB808D679F777BC6702C7D", + "39F23369A9D9BACFA530E26304231461" }; + String[] inputs = new String[] { + "6bc1bee22e409f96e93d7e117393172a", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "30c81c46a35ce411e5fbc1191a0a52ef", + "f69f2445df4f9b17ad2b417be66c3710", + "6bc1bee22e409f96e93d7e117393172a", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "30c81c46a35ce411e5fbc1191a0a52ef", + "f69f2445df4f9b17ad2b417be66c3710", + "6bc1bee22e409f96e93d7e117393172a", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "30c81c46a35ce411e5fbc1191a0a52ef", + "f69f2445df4f9b17ad2b417be66c3710" }; + String[] outputs = new String[] { + "7649abac8119b246cee98e9b12e9197d", + "5086cb9b507219ee95db113a917678b2", + "73bed6b8e3c1743b7116e69e22229516", + "3ff1caa1681fac09120eca307586e1a7", + "4f021db243bc633d7178183a9fa071e8", + "b4d9ada9ad7dedf4e5e738763f69145a", + "571b242012fb7ae07fa9baac3df102e0", + "08b0e27988598881d920a9e64f5615cd", + "f58c4c04d6e5f1ba779eabfb5f7bfbd6", + "9cfc4e967edb808d679f777bc6702c7d", + "39f23369a9d9bacfa530e26304231461", + "b2eb05e2c39be9fcda6c19078c6a9d1b" }; + + ByteBuffer input = ByteBuffer.allocateDirect(Aes.BLOCK_SIZE); + ByteBuffer output = ByteBuffer.allocateDirect(Aes.BLOCK_SIZE); + ByteBuffer plain = ByteBuffer.allocateDirect(Aes.BLOCK_SIZE); + ByteBuffer cipher = ByteBuffer.allocateDirect(Aes.BLOCK_SIZE); + + for (int i = 0; i < inputs.length; i++) { + Aes enc = new Aes(Util.h2b(keys[i]), Util.h2b(ivs[i]), + Aes.ENCRYPT_MODE); + Aes dec = new Aes(Util.h2b(keys[i]), Util.h2b(ivs[i]), + Aes.DECRYPT_MODE); + + input.put(Util.h2b(inputs[i])).rewind(); + output.put(Util.h2b(outputs[i])).rewind(); + + try { + assertEquals(Aes.BLOCK_SIZE, enc.update(input, cipher)); + assertEquals(Aes.BLOCK_SIZE, dec.update(output, plain)); + } catch (ShortBufferException e) { + e.printStackTrace(); + fail(); + } + + assertEquals(Aes.BLOCK_SIZE, input.position()); + assertEquals(0, input.remaining()); + assertEquals(Aes.BLOCK_SIZE, output.position()); + assertEquals(0, output.remaining()); + assertEquals(Aes.BLOCK_SIZE, cipher.position()); + assertEquals(0, cipher.remaining()); + assertEquals(Aes.BLOCK_SIZE, plain.position()); + assertEquals(0, plain.remaining()); + + input.rewind(); + output.rewind(); + cipher.rewind(); + plain.rewind(); + + assertEquals(output, cipher); + assertEquals(input, plain); + + /* tests ShortBufferException */ + if (i == inputs.length - 1) { + cipher.position(cipher.limit()); + enc.update(input, cipher); + } + } + } + + @Test(expected=ShortBufferException.class) + public void updateShouldMatchUsingByteArray() throws ShortBufferException { + String[] keys = new String[] { + "2b7e151628aed2a6abf7158809cf4f3c", + "2b7e151628aed2a6abf7158809cf4f3c", + "2b7e151628aed2a6abf7158809cf4f3c", + "2b7e151628aed2a6abf7158809cf4f3c", + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", }; + String[] ivs = new String[] { + "000102030405060708090A0B0C0D0E0F", + "7649ABAC8119B246CEE98E9B12E9197D", + "5086CB9B507219EE95DB113A917678B2", + "73BED6B8E3C1743B7116E69E22229516", + "000102030405060708090A0B0C0D0E0F", + "4F021DB243BC633D7178183A9FA071E8", + "B4D9ADA9AD7DEDF4E5E738763F69145A", + "571B242012FB7AE07FA9BAAC3DF102E0", + "000102030405060708090A0B0C0D0E0F", + "F58C4C04D6E5F1BA779EABFB5F7BFBD6", + "9CFC4E967EDB808D679F777BC6702C7D", + "39F23369A9D9BACFA530E26304231461" }; + String[] inputs = new String[] { + "6bc1bee22e409f96e93d7e117393172a", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "30c81c46a35ce411e5fbc1191a0a52ef", + "f69f2445df4f9b17ad2b417be66c3710", + "6bc1bee22e409f96e93d7e117393172a", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "30c81c46a35ce411e5fbc1191a0a52ef", + "f69f2445df4f9b17ad2b417be66c3710", + "6bc1bee22e409f96e93d7e117393172a", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "30c81c46a35ce411e5fbc1191a0a52ef", + "f69f2445df4f9b17ad2b417be66c3710" }; + String[] outputs = new String[] { + "7649abac8119b246cee98e9b12e9197d", + "5086cb9b507219ee95db113a917678b2", + "73bed6b8e3c1743b7116e69e22229516", + "3ff1caa1681fac09120eca307586e1a7", + "4f021db243bc633d7178183a9fa071e8", + "b4d9ada9ad7dedf4e5e738763f69145a", + "571b242012fb7ae07fa9baac3df102e0", + "08b0e27988598881d920a9e64f5615cd", + "f58c4c04d6e5f1ba779eabfb5f7bfbd6", + "9cfc4e967edb808d679f777bc6702c7d", + "39f23369a9d9bacfa530e26304231461", + "b2eb05e2c39be9fcda6c19078c6a9d1b" }; + + for (int i = 0; i < inputs.length; i++) { + Aes enc = new Aes(Util.h2b(keys[i]), Util.h2b(ivs[i]), + Aes.ENCRYPT_MODE); + Aes dec = new Aes(Util.h2b(keys[i]), Util.h2b(ivs[i]), + Aes.DECRYPT_MODE); + + byte[] input = Util.h2b(inputs[i]); + byte[] output = Util.h2b(outputs[i]); + byte[] cipher = new byte[Aes.BLOCK_SIZE]; + byte[] plain = new byte[Aes.BLOCK_SIZE]; + + if (i % 2 == 0) { + cipher = enc.update(input, 0, input.length); + plain = dec.update(output, 0, output.length); + } else { + try { + assertEquals(Aes.BLOCK_SIZE, + enc.update(input, 0, input.length, cipher, 0)); + assertEquals(Aes.BLOCK_SIZE, + dec.update(output, 0, output.length, plain, 0)); + } catch (ShortBufferException e) { + e.printStackTrace(); + fail(); + } + } + + assertArrayEquals(output, cipher); + assertArrayEquals(input, plain); + + /* tests ShortBufferException */ + if (i == inputs.length - 1) + enc.update(input, 0, input.length, cipher, Aes.BLOCK_SIZE); + } + } }