diff --git a/README_JCE.md b/README_JCE.md index df96c16e..a27ae686 100644 --- a/README_JCE.md +++ b/README_JCE.md @@ -177,6 +177,9 @@ The JCE provider currently supports the following algorithms: EC DH + KeyFactory + EC + CertPathValidator Class PKIX diff --git a/jni/include/com_wolfssl_wolfcrypt_Ecc.h b/jni/include/com_wolfssl_wolfcrypt_Ecc.h index 4bf24f5f..1989ed50 100644 --- a/jni/include/com_wolfssl_wolfcrypt_Ecc.h +++ b/jni/include/com_wolfssl_wolfcrypt_Ecc.h @@ -177,6 +177,62 @@ JNIEXPORT jstring JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1get_1curve_1na JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1get_1curve_1id_1from_1params (JNIEnv *, jclass, jint, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jint); +/* + * Class: com_wolfssl_wolfcrypt_Ecc + * Method: wc_ecc_get_curve_params_from_name + * Signature: (Ljava/lang/String;)[Ljava/lang/String; + */ +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1get_1curve_1params_1from_1name + (JNIEnv *, jclass, jstring); + +/* + * Class: com_wolfssl_wolfcrypt_Ecc + * Method: wc_ecc_get_all_curve_names + * Signature: ()[Ljava/lang/String; + */ +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1get_1all_1curve_1names + (JNIEnv *, jclass); + +/* + * Class: com_wolfssl_wolfcrypt_Ecc + * Method: wc_ecc_export_private_raw + * Signature: ()[B + */ +JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1export_1private_1raw + (JNIEnv *, jobject); + +/* + * Class: com_wolfssl_wolfcrypt_Ecc + * Method: wc_ecc_export_public_raw + * Signature: ()[[B + */ +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1export_1public_1raw + (JNIEnv *, jobject); + +/* + * Class: com_wolfssl_wolfcrypt_Ecc + * Method: wc_ecc_import_private_raw + * Signature: ([BLjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1import_1private_1raw + (JNIEnv *, jobject, jbyteArray, jstring); + +/* + * Class: com_wolfssl_wolfcrypt_Ecc + * Method: wc_ecc_import_public_raw + * Signature: ([B[BLjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1import_1public_1raw + (JNIEnv *, jobject, jbyteArray, jbyteArray, jstring); + +/* + * Class: com_wolfssl_wolfcrypt_Ecc + * Method: wc_ecc_get_curve_id + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1get_1curve_1id + (JNIEnv *, jobject); + #ifdef __cplusplus } #endif diff --git a/jni/jni_ecc.c b/jni/jni_ecc.c index c97fe9fa..58b2ff17 100644 --- a/jni/jni_ecc.c +++ b/jni/jni_ecc.c @@ -20,6 +20,7 @@ */ #include +#include #ifdef WOLFSSL_USER_SETTINGS #include @@ -42,6 +43,12 @@ #define RNG WC_RNG #endif +/* FIPSv2 does not have ECC_CURVE_MAX. 28 is what this value would be + * if it existed in the FIPSv2 wolfssl/wolfcrypt/ecc.h header. */ +#if defined(HAVE_FIPS) && defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION == 2) + #define ECC_CURVE_MAX 27 +#endif + JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_Ecc_mallocNativeStruct( JNIEnv* env, jobject this) @@ -1154,25 +1161,611 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1get_1curve_1id_1f byte* Gy = getByteArray(env, gy_object); word32 GySz = getByteArrayLength(env, gy_object); - if (prime == NULL || Af == NULL || Bf == NULL || - order == NULL || Gx == NULL || Gy == NULL) { + if (prime == NULL || Af == NULL || Bf == NULL || order == NULL || + Gx == NULL || Gy == NULL) { ret = BAD_FUNC_ARG; } else { ret = wc_ecc_get_curve_id_from_params(fieldSz, prime, primeSz, - Af, AfSz, Bf, BfSz, order, orderSz, Gx, GxSz, - Gy, GySz, cofactor); + Af, AfSz, Bf, BfSz, order, orderSz, Gx, GxSz, Gy, GySz, cofactor); } - if (ret < 0) { + LogStr("wc_ecc_get_curve_id_from_params() = %d\n", ret); +#else + throwNotCompiledInException(env); +#endif + + return ret; +} + +JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1export_1private_1raw + (JNIEnv* env, jobject this) +{ + jbyteArray result = NULL; + +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) + int ret = 0; + ecc_key* ecc = NULL; + byte* output = NULL; + word32 outputSz = 0; + + ecc = (ecc_key*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception, prevent throwing another */ + return NULL; + } + + if (ecc == NULL) { + ret = BAD_FUNC_ARG; + } + + if (ret == 0) { + outputSz = wc_ecc_size(ecc); + output = (byte*)XMALLOC(outputSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (output == NULL) { + ret = MEMORY_E; + } + else { + XMEMSET(output, 0, outputSz); + } + } + + if (ret == 0) { + PRIVATE_KEY_UNLOCK(); + ret = wc_ecc_export_private_only(ecc, output, &outputSz); + PRIVATE_KEY_LOCK(); + } + + if (ret == 0) { + result = (*env)->NewByteArray(env, outputSz); + + if (result) { + (*env)->SetByteArrayRegion(env, result, 0, outputSz, + (const jbyte*) output); + } else { + throwWolfCryptException(env, "Failed to allocate raw private key"); + } + } else { throwWolfCryptExceptionFromError(env, ret); } - LogStr("wc_ecc_get_curve_id_from_params() = %d\n", ret); + LogStr("wc_ecc_export_private_raw(ecc=%p, output=%p, outputSz) = %d\n", + ecc, output, ret); + + if (output != NULL) { + XMEMSET(output, 0, outputSz); + XFREE(output, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } #else throwNotCompiledInException(env); #endif - return ret; + return result; +} + +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1export_1public_1raw + (JNIEnv* env, jobject this) +{ + jobjectArray result = NULL; + +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) + int ret = 0; + ecc_key* ecc = NULL; + byte* x = NULL; + byte* y = NULL; + word32 xSz = 0; + word32 ySz = 0; + jbyteArray xArray = NULL; + jbyteArray yArray = NULL; + jclass byteArrayClass = NULL; + + ecc = (ecc_key*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception, prevent throwing another */ + return NULL; + } + + if (ecc == NULL) { + ret = BAD_FUNC_ARG; + } + + if (ret == 0) { + xSz = wc_ecc_size(ecc); + ySz = xSz; + x = (byte*)XMALLOC(xSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + y = (byte*)XMALLOC(ySz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (x == NULL || y == NULL) { + ret = MEMORY_E; + } + else { + XMEMSET(x, 0, xSz); + XMEMSET(y, 0, ySz); + } + } + + if (ret == 0) { + ret = wc_ecc_export_public_raw(ecc, x, &xSz, y, &ySz); + } + + if (ret == 0) { + byteArrayClass = (*env)->FindClass(env, "[B"); + if (byteArrayClass == NULL) { + ret = MEMORY_E; + } else { + result = (*env)->NewObjectArray(env, 2, byteArrayClass, NULL); + } + } + + if (ret == 0 && result != NULL) { + xArray = (*env)->NewByteArray(env, xSz); + yArray = (*env)->NewByteArray(env, ySz); + + if (xArray != NULL && yArray != NULL) { + (*env)->SetByteArrayRegion(env, xArray, 0, xSz, (const jbyte*)x); + (*env)->SetByteArrayRegion(env, yArray, 0, ySz, (const jbyte*)y); + (*env)->SetObjectArrayElement(env, result, 0, xArray); + (*env)->SetObjectArrayElement(env, result, 1, yArray); + } else { + throwWolfCryptException(env, + "Failed to allocate coordinate arrays"); + ret = -1; + } + } + + if (ret != 0) { + /* If error, free any array we may have created */ + if (xArray != NULL) { + (*env)->DeleteLocalRef(env, xArray); + } + if (yArray != NULL) { + (*env)->DeleteLocalRef(env, yArray); + } + if (result != NULL) { + (*env)->DeleteLocalRef(env, result); + result = NULL; + } + throwWolfCryptExceptionFromError(env, ret); + } + + LogStr("wc_ecc_export_public_raw(ecc=%p, x=%p, y=%p) = %d\n", + ecc, x, y, ret); + + if (x != NULL) { + XMEMSET(x, 0, xSz); + XFREE(x, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + if (y != NULL) { + XMEMSET(y, 0, ySz); + XFREE(y, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } +#else + throwNotCompiledInException(env); +#endif + + return result; +} + +JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1import_1private_1raw + (JNIEnv* env, jobject this, jbyteArray priv_object, jstring curveName) +{ +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_IMPORT) + int ret = 0; + ecc_key* ecc = NULL; + byte* privKey = NULL; + word32 privKeySz = 0; + const char* name = NULL; + int curveId = 0; + + ecc = (ecc_key*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception, prevent throwing another */ + return; + } + + privKey = getByteArray(env, priv_object); + privKeySz = getByteArrayLength(env, priv_object); + + if (ecc == NULL || privKey == NULL || curveName == NULL) { + ret = BAD_FUNC_ARG; + } + + if (ret == 0) { + name = (*env)->GetStringUTFChars(env, curveName, 0); + if (name == NULL) { + ret = BAD_FUNC_ARG; + } + } + + if (ret == 0) { + curveId = wc_ecc_get_curve_id_from_name(name); + (*env)->ReleaseStringUTFChars(env, curveName, name); + + if (curveId < 0) { + ret = BAD_FUNC_ARG; + } + } + + if (ret == 0) { + /* Initialize ECC key structure */ + ret = wc_ecc_init(ecc); + } + + if (ret == 0) { + ret = wc_ecc_import_private_key(privKey, privKeySz, NULL, 0, ecc); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } + + LogStr("wc_ecc_import_unsigned(ecc=%p, privKey=%p) = %d\n", + ecc, privKey, ret); + + releaseByteArray(env, priv_object, privKey, JNI_ABORT); +#else + throwNotCompiledInException(env); +#endif +} + +JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1import_1public_1raw + (JNIEnv* env, jobject this, jbyteArray x_object, jbyteArray y_object, + jstring curveName) +{ +#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_IMPORT) + int ret = 0; + ecc_key* ecc = NULL; + byte* x = NULL; + byte* y = NULL; + word32 xSz = 0; + word32 ySz = 0; + const char* name = NULL; + int curveId = 0; + word32 expectedSz = 0; + + ecc = (ecc_key*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception, prevent throwing another */ + return; + } + + x = getByteArray(env, x_object); + xSz = getByteArrayLength(env, x_object); + y = getByteArray(env, y_object); + ySz = getByteArrayLength(env, y_object); + + if (ecc == NULL || x == NULL || y == NULL || curveName == NULL) { + ret = BAD_FUNC_ARG; + } + + if (ret == 0) { + name = (*env)->GetStringUTFChars(env, curveName, 0); + if (name == NULL) { + ret = BAD_FUNC_ARG; + } + } + + if (ret == 0) { + curveId = wc_ecc_get_curve_id_from_name(name); + /* Get expected size for curve */ + expectedSz = wc_ecc_get_curve_size_from_id(curveId); + (*env)->ReleaseStringUTFChars(env, curveName, name); + + if (curveId < 0 || expectedSz <= 0) { + ret = BAD_FUNC_ARG; + } + } + + if (xSz != expectedSz || ySz != expectedSz) { + LogStr("ECC x or y size does not match expected size for curve\n"); + ret = BAD_FUNC_ARG; + } + + if (ret == 0) { + ret = wc_ecc_init(ecc); + } + + if (ret == 0) { + ret = wc_ecc_import_unsigned(ecc, x, y, NULL, curveId); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } + + LogStr("wc_ecc_import_unsigned(ecc=%p, x=%p, y=%p, expectedSz=%d) = %d\n", + ecc, x, y, expectedSz, ret); + + releaseByteArray(env, x_object, x, JNI_ABORT); + releaseByteArray(env, y_object, y, JNI_ABORT); +#else + throwNotCompiledInException(env); +#endif +} + +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1get_1curve_1id + (JNIEnv* env, jobject this) +{ + jint result = 0; +#ifdef HAVE_ECC + ecc_key* ecc = NULL; + + ecc = (ecc_key*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception, prevent throwing another */ + return -1; + } + + if (ecc == NULL) { + throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG); + return -1; + } + + if (ecc->dp != NULL) { + result = ecc->dp->id; + + } else { + throwWolfCryptException(env, "No curve parameters available"); + return -1; + } + + LogStr("ecc->dp->id = %d\n", result); +#else + throwNotCompiledInException(env); +#endif + + return result; +} + +/* + * Returns String[] with curve parameters in the following order, based on + * provided input curve name: + * + * [0] prime - field prime as hex string + * [1] a - curve coefficient a as hex string + * [2] b - curve coefficient b as hex string + * [3] order - curve order as hex string + * [4] gx - generator point x coordinate as hex string + * [5] gy - generator point y coordinate as hex string + * [6] cofactor - cofactor as decimal string + * + * Returns NULL on error, otherwise valid String[]. + */ +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1get_1curve_1params_1from_1name + (JNIEnv* env, jclass this, jstring curveName) +{ + jobjectArray result = NULL; +#ifdef HAVE_ECC + int i; + int ret; + const char* name = NULL; + int curveIdx = 0; + const ecc_set_type* dp = NULL; + jstring paramStrings[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + char cofactorStr[32]; +#if defined(HAVE_FIPS) && \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION == 2)) + int curveId = 0; + ecc_key tempKey; +#endif + + if (curveName == NULL) { + throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG); + return NULL; + } + + name = (*env)->GetStringUTFChars(env, curveName, 0); + if (name == NULL) { + throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG); + return NULL; + } + + /* Get curve index from name */ + curveIdx = wc_ecc_get_curve_idx_from_name(name); + if (curveIdx < 0) { + LogStr("wc_ecc_get_curve_idx_from_name failed, idx: %d\n", curveIdx); + throwWolfCryptExceptionFromError(env, curveIdx); + return NULL; + } + + LogStr("wc_ecc_get_curve_idx_from_name(name=%s) = %d\n", name, curveIdx); + +#if defined(HAVE_FIPS) && \ + (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION == 2)) + /* Get curve parameters by creating a temporary ECC key with the curve. + * wc_ecc_get_curve_params() exists in current wolfSSL but not older + * FIPSv2 bundles. */ + curveId = wc_ecc_get_curve_id_from_name(name); + (*env)->ReleaseStringUTFChars(env, curveName, name); + + if (curveId < 0) { + throwWolfCryptExceptionFromError(env, curveId); + return NULL; + } + + ret = wc_ecc_init(&tempKey); + if (ret == 0) { + ret = wc_ecc_set_curve(&tempKey, 0, curveId); + if ((ret == 0) && (tempKey.dp != NULL)) { + dp = tempKey.dp; + LogStr("tempKey.dp = %p (id=%d)\n", dp, dp->id); + } + wc_ecc_free(&tempKey); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } +#else + /* Get curve parameters directly */ + dp = wc_ecc_get_curve_params(curveIdx); + (*env)->ReleaseStringUTFChars(env, curveName, name); +#endif /* HAVE_FIPS & HAVE_FIPS_VERSION */ + + if (dp == NULL) { + throwWolfCryptExceptionFromError(env, ECC_CURVE_OID_E); + return NULL; + } + + /* Create Java String[7], freed by Java when method returns if we + * return in an error state since this is a local reference. */ + result = (*env)->NewObjectArray(env, 7, + (*env)->FindClass(env, "java/lang/String"), NULL); + if (result == NULL) { + throwWolfCryptExceptionFromError(env, MEMORY_E); + return NULL; + } + + /* Convert curve parameters to Java strings: + * dp->prime = field prime + * dp->Af = curve coefficient a + * dp->Bf = curve coefficient b + * dp->order = curve order + * dp->Gx = generator x + * dp->Gy = generator y + */ + paramStrings[0] = (*env)->NewStringUTF(env, dp->prime); + paramStrings[1] = (*env)->NewStringUTF(env, dp->Af); + paramStrings[2] = (*env)->NewStringUTF(env, dp->Bf); + paramStrings[3] = (*env)->NewStringUTF(env, dp->order); + paramStrings[4] = (*env)->NewStringUTF(env, dp->Gx); + paramStrings[5] = (*env)->NewStringUTF(env, dp->Gy); + + /* Convert cofactor to string */ + ret = XSNPRINTF(cofactorStr, sizeof(cofactorStr), "%d", dp->cofactor); + if (ret < 0 || ret >= (int)sizeof(cofactorStr)) { + throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG); + return NULL; + } + paramStrings[6] = (*env)->NewStringUTF(env, cofactorStr); + + /* Set array elements */ + for (i = 0; i < 7; i++) { + if (paramStrings[i] == NULL) { + return NULL; + } + (*env)->SetObjectArrayElement(env, result, i, paramStrings[i]); + (*env)->DeleteLocalRef(env, paramStrings[i]); + } + + LogStr("wc_ecc_get_curve_params_from_name(curveName=%s) = success\n", + dp->name); + +#else + throwNotCompiledInException(env); +#endif + + return result; +} + +/* + * Get all curve names supported by the compiled wolfCrypt library. + * + * Return String[] containing all available curve names, or NULL on error. + */ +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1get_1all_1curve_1names + (JNIEnv* env, jclass this) +{ + jobjectArray result = NULL; +#ifdef HAVE_ECC + jstring* curveNames = NULL; + int curveCount = 0; + int i; + int j; + int maxIdx = 0; + + /* First pass: find maximum valid curve index by testing consecutive + * indices until we find an invalid one */ + for (i = 0; i < ECC_CURVE_MAX; i++) { + if (wc_ecc_is_valid_idx(i)) { + maxIdx = i; + } + } + + if (maxIdx == 0) { + throwWolfCryptExceptionFromError(env, ECC_CURVE_OID_E); + return NULL; + } + + /* Second pass: count valid curves with names */ + for (i = 0; i <= maxIdx; i++) { + if (wc_ecc_is_valid_idx(i)) { + const char* name = wc_ecc_get_name(wc_ecc_get_curve_id(i)); + if (name != NULL) { + curveCount++; + } + } + } + + if (curveCount == 0) { + throwWolfCryptExceptionFromError(env, ECC_CURVE_OID_E); + return NULL; + } + + /* Dynamically allocate array based on counted curves */ + curveNames = (jstring*)XMALLOC(curveCount * sizeof(jstring), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (curveNames == NULL) { + throwWolfCryptExceptionFromError(env, MEMORY_E); + return NULL; + } + + /* Third pass: collect curve names */ + curveCount = 0; + for (i = 0; i <= maxIdx; i++) { + if (wc_ecc_is_valid_idx(i)) { + const char* name = wc_ecc_get_name(wc_ecc_get_curve_id(i)); + if (name != NULL) { + curveNames[curveCount] = (*env)->NewStringUTF(env, name); + if (curveNames[curveCount] == NULL) { + /* Clean up any previously created strings */ + for (j = 0; j < curveCount; j++) { + (*env)->DeleteLocalRef(env, curveNames[j]); + } + XFREE(curveNames, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwWolfCryptExceptionFromError(env, MEMORY_E); + return NULL; + } + curveCount++; + } + } + } + + if (curveCount == 0) { + XFREE(curveNames, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwWolfCryptExceptionFromError(env, ECC_CURVE_OID_E); + return NULL; + } + + /* Create Java String array with actual count */ + result = (*env)->NewObjectArray(env, curveCount, + (*env)->FindClass(env, "java/lang/String"), NULL); + if (result == NULL) { + /* Clean up curve name strings */ + for (i = 0; i < curveCount; i++) { + (*env)->DeleteLocalRef(env, curveNames[i]); + } + XFREE(curveNames, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwWolfCryptExceptionFromError(env, MEMORY_E); + return NULL; + } + + /* Fill array with collected curve names */ + for (i = 0; i < curveCount; i++) { + (*env)->SetObjectArrayElement(env, result, i, curveNames[i]); + (*env)->DeleteLocalRef(env, curveNames[i]); + } + + /* Clean up dynamic array */ + XFREE(curveNames, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + LogStr("wc_ecc_get_all_curve_names() found %d curves (maxIdx: %d)\n", + curveCount, maxIdx); + +#else + throwNotCompiledInException(env); +#endif + + return result; } diff --git a/scripts/infer.sh b/scripts/infer.sh index 1a46e484..fe0731bb 100755 --- a/scripts/infer.sh +++ b/scripts/infer.sh @@ -73,6 +73,10 @@ infer --fail-on-issue run -- javac \ src/main/java/com/wolfssl/provider/jce/WolfCryptAesParameters.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptDebug.java \ + src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java \ + src/main/java/com/wolfssl/provider/jce/WolfCryptECParameterSpec.java \ + src/main/java/com/wolfssl/provider/jce/WolfCryptECPrivateKey.java \ + src/main/java/com/wolfssl/provider/jce/WolfCryptECPublicKey.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptGcmParameters.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptKeyGenerator.java \ diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java new file mode 100644 index 00000000..731a26bf --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java @@ -0,0 +1,792 @@ +/* WolfCryptECKeyFactory.java + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +package com.wolfssl.provider.jce; + +import java.util.Arrays; +import java.math.BigInteger; +import java.security.Key; +import java.security.KeyFactorySpi; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.InvalidKeyException; +import java.security.spec.KeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.InvalidAlgorithmParameterException; + +import com.wolfssl.wolfcrypt.Ecc; +import com.wolfssl.wolfcrypt.WolfCryptException; + +/** + * wolfCrypt JCE EC KeyFactory implementation. + * + * This class provides key conversion capabilities for Elliptic Curve (EC) + * keys, supporting conversion between various KeySpec formats and Key objects. + */ +public class WolfCryptECKeyFactory extends KeyFactorySpi { + + /** + * Create new WolfCryptECKeyFactory object. + */ + public WolfCryptECKeyFactory() { + log("created new EC KeyFactory"); + } + + /** + * Internal method for logging output. + * + * @param msg message to be logged + */ + private void log(String msg) { + WolfCryptDebug.log(getClass(), WolfCryptDebug.INFO, + () -> "[EC KeyFactory] " + msg); + } + + /** + * Generate a private key object from the provided key specification. + * + * @param keySpec the KeySpec of the private key + * + * @return the private key object + * + * @throws InvalidKeySpecException if the given key specification + * is inappropriate for this KeyFactory to produce a private key. + * Currently supported KeySpec types are: PKCS8EncodedKeySpec + * and ECPrivateKeySpec + */ + @Override + protected PrivateKey engineGeneratePrivate(KeySpec keySpec) + throws InvalidKeySpecException { + + log("generating ECPrivateKey from KeySpec"); + + if (keySpec == null) { + throw new InvalidKeySpecException("KeySpec cannot be null"); + } + + if (keySpec instanceof PKCS8EncodedKeySpec) { + return generatePrivateFromPKCS8((PKCS8EncodedKeySpec)keySpec); + } + else if (keySpec instanceof ECPrivateKeySpec) { + return generatePrivateFromECSpec((ECPrivateKeySpec)keySpec); + } + else { + throw new InvalidKeySpecException( + "Unsupported KeySpec type: " + keySpec.getClass().getName()); + } + } + + /** + * Generates a public key object from the provided key specification. + * + * @param keySpec the KeySpec of the public key + * + * @return the public key object + * + * @throws InvalidKeySpecException if the given key specification + * is inappropriate for this KeyFactory to produce a public key. + * Currently supported KeySpec types are: X509EncodedKeySpec + * and ECPublicKeySpec. + */ + @Override + protected PublicKey engineGeneratePublic(KeySpec keySpec) + throws InvalidKeySpecException { + + log("generating ECPublicKey from KeySpec"); + + if (keySpec == null) { + throw new InvalidKeySpecException("KeySpec cannot be null"); + } + + if (keySpec instanceof X509EncodedKeySpec) { + return generatePublicFromX509((X509EncodedKeySpec)keySpec); + } + else if (keySpec instanceof ECPublicKeySpec) { + return generatePublicFromECSpec((ECPublicKeySpec)keySpec); + } + else { + throw new InvalidKeySpecException( + "Unsupported KeySpec type: " + keySpec.getClass().getName()); + } + } + + /** + * Returns a KeySpec of the given Key object in the requested format. + * + * @param key the Key object, must be one of ECPrivateKey or ECPublicKey + * @param keySpec the requested format in which the key material shall + * be returned + * + * @return a KeySpec in the requested format matching the input Key + * + * @throws InvalidKeySpecException if the requested key specification + * is inappropriate for the given key, or the provided key cannot + * be processed by this key factory. + */ + @Override + protected T engineGetKeySpec(Key key, + Class keySpec) throws InvalidKeySpecException { + + log("returning KeySpec from Key in requested type"); + + if (key == null) { + throw new InvalidKeySpecException("Key cannot be null"); + } + + if (keySpec == null) { + throw new InvalidKeySpecException( + "Requested KeySpec format cannot be null"); + } + + if (key instanceof ECPrivateKey) { + return getPrivateKeySpec((ECPrivateKey)key, keySpec); + } + else if (key instanceof ECPublicKey) { + return getPublicKeySpec((ECPublicKey)key, keySpec); + } + else { + throw new InvalidKeySpecException( + "Unsupported Key type: " + key.getClass().getName()); + } + } + + /** + * Translates a Key object, whose provider may be unknown or potentially + * untrusted, into a corresponding Key object of this KeyFactory. + * + * @param key the Key to be translated, must be one of ECPrivateKey + * or ECPublicKey + * + * @return the translated Key + * + * @throws InvalidKeyException if the given key cannot be processed + * by this KeyFactory + */ + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + + log("translating Key to wolfJCE EC KeyFactory type"); + + if (key == null) { + throw new InvalidKeyException("Key cannot be null"); + } + + if (key instanceof ECPrivateKey) { + return translatePrivateKey((ECPrivateKey)key); + } + else if (key instanceof ECPublicKey) { + return translatePublicKey((ECPublicKey)key); + } + else { + throw new InvalidKeyException( + "Unsupported Key type: " + key.getClass().getName()); + } + } + + /** + * Private helper method for generating ECPrivateKey from + * PKCS8EncodedKeySpec. + * + * @param keySpec the PKCS8EncodedKeySpec containing the private key + * + * @return the generated ECPrivateKey + * + * @throws InvalidKeySpecException if the key specification is invalid + */ + private PrivateKey generatePrivateFromPKCS8(PKCS8EncodedKeySpec keySpec) + throws InvalidKeySpecException { + + byte[] pkcs8Der = null; + byte[] privDer = null; + Ecc ecc = null; + + try { + if (keySpec == null) { + throw new InvalidKeySpecException( + "PKCS8EncodedKeySpec cannot be null"); + } + + /* Get DER-encoded PKCS#8 data from spec */ + pkcs8Der = keySpec.getEncoded(); + if (pkcs8Der == null) { + throw new InvalidKeySpecException( + "PKCS8EncodedKeySpec contains null encoded key"); + } + + log("decoding PKCS8 private key, length: " + pkcs8Der.length); + + /* Read into Ecc object, validates PKCS#8 structure via wolfCrypt */ + ecc = new Ecc(); + ecc.privateKeyDecode(pkcs8Der); + + /* Export as PKCS#8, ensures proper wolfCrypt DER encoding */ + privDer = ecc.privateKeyEncodePKCS8(); + if (privDer == null) { + throw new InvalidKeySpecException( + "Failed to export private key as DER from Ecc"); + } + + /* Create wolfJCE ECPrivateKey object */ + return new WolfCryptECPrivateKey(privDer); + + } catch (WolfCryptException e) { + throw new InvalidKeySpecException( + "wolfCrypt error during PKCS8 key decode: " + + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + if (privDer != null) { + Arrays.fill(privDer, (byte)0); + } + } + } + + /** + * Private helper method for generating ECPrivateKey from + * ECPrivateKeySpec. + * + * @param keySpec the ECPrivateKeySpec containing the private key + * + * @return the generated ECPrivateKey + * + * @throws InvalidKeySpecException if the key specification is invalid + */ + private PrivateKey generatePrivateFromECSpec(ECPrivateKeySpec keySpec) + throws InvalidKeySpecException { + + Ecc ecc = null; + byte[] pkcs8Data = null; + byte[] privBytes = null; + String curveName = null; + + try { + log("generating ECPrivateKey from ECPrivateKeySpec"); + + if (keySpec == null) { + throw new InvalidKeySpecException( + "ECPrivateKeySpec cannot be null"); + } + if (keySpec.getS() == null) { + throw new InvalidKeySpecException( + "Private key value cannot be null"); + } + if (keySpec.getParams() == null) { + throw new InvalidKeySpecException( + "ECParameterSpec cannot be null"); + } + + /* Validate ECParameterSpec is supported by wolfCrypt, + * throws WolfCryptException if invalid */ + WolfCryptECParameterSpec.validateParameters(keySpec.getParams()); + + /* Get curve name from ECParameterSpec */ + try { + curveName = WolfCryptECParameterSpec.getCurveName( + keySpec.getParams()); + + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidKeySpecException( + "Unsupported curve parameters: " + e.getMessage(), e); + } + + if (curveName == null || curveName.isEmpty()) { + throw new InvalidKeySpecException( + "Unable to determine curve name from ECParameterSpec"); + } + + /* Convert BigInteger private key to byte[] */ + privBytes = convertPrivateValueToBytes(keySpec.getS(), curveName); + + /* Import private key into Ecc object */ + ecc = new Ecc(); + ecc.importPrivateRaw(privBytes, curveName); + + /* Export as PKCS#8 format */ + pkcs8Data = ecc.privateKeyEncodePKCS8(); + if (pkcs8Data == null) { + throw new InvalidKeySpecException( + "Failed to export private key as PKCS#8"); + } + + /* Create wolfJCE ECPrivateKey object */ + return new WolfCryptECPrivateKey(pkcs8Data); + + } catch (WolfCryptException e) { + throw new InvalidKeySpecException( + "wolfCrypt error during ECPrivateKeySpec conversion: " + + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + if (pkcs8Data != null) { + Arrays.fill(pkcs8Data, (byte)0); + } + if (privBytes != null) { + Arrays.fill(privBytes, (byte)0); + } + } + } + + /** + * Validates and converts BigInteger to byte array for wolfCrypt. + * + * Handles BigInteger.toByteArray() edge cases including: + * - Leading zero byte for sign bit when MSB is set + * - Padding with leading zeros if value is shorter than expected + * - Validation that value does not exceed curve size + * + * @param value the BigInteger value to convert + * @param fieldName description of the field for error messages + * @param curveName curve name for size calculation and error messages + * @return properly formatted byte array for wolfCrypt import + * @throws InvalidKeySpecException if the value is invalid + */ + private byte[] validateAndConvertBigIntegerToBytes(BigInteger value, + String fieldName, String curveName) throws InvalidKeySpecException { + + int expectedSz = 0; + byte[] bytes = null; + byte[] result = null; + + try { + /* Get expected byte array size for this curve */ + expectedSz = Ecc.getCurveSizeFromName(curveName); + + /* Validate value is non-negative */ + if (value.signum() < 0) { + throw new InvalidKeySpecException( + fieldName + " cannot be negative"); + } + + /* Convert to byte array */ + bytes = value.toByteArray(); + + /* Ensure result is exactly expected size, accounting for + * BigInteger toByteArray() edge cases: + * + * If MSB is set, toByteArray() adds a leading 0x00 (sign bit) + * Bytes can be up to expectedSz + 1 due to sign bit */ + if ((bytes.length > expectedSz + 1) || + (bytes.length == expectedSz + 1 && bytes[0] != 0)) { + throw new InvalidKeySpecException( + fieldName + " too large for curve " + curveName); + } + + /* Remove leading zero if present (sign bit from BigInteger) */ + if ((bytes.length == expectedSz + 1) && (bytes[0] == 0)) { + result = new byte[expectedSz]; + System.arraycopy(bytes, 1, result, 0, expectedSz); + /* Zero out original array */ + Arrays.fill(bytes, (byte)0); + } + /* If array is shorter than expected, pad with leading zeros */ + else if (bytes.length < expectedSz) { + result = new byte[expectedSz]; + int offset = expectedSz - bytes.length; + System.arraycopy(bytes, 0, result, offset, bytes.length); + /* Zero out original array */ + Arrays.fill(bytes, (byte)0); + } + else { + /* Array is correct size, use as-is */ + result = bytes; + bytes = null; /* Prevent cleanup since we're returning this */ + } + + return result; + + } catch (WolfCryptException e) { + /* Clean up any allocated arrays on error */ + if (bytes != null) { + Arrays.fill(bytes, (byte)0); + } + if (result != null) { + Arrays.fill(result, (byte)0); + } + throw new InvalidKeySpecException( + "Error getting curve size for " + curveName + ": " + + e.getMessage(), e); + } + } + + /** + * Convert BigInteger private key to byte[]. + * + * @param privateValue private key BigInteger + * @param curveName curve name for size validation + * + * @return properly formatted byte array for wolfCrypt import + * + * @throws InvalidKeySpecException if the private value is invalid + */ + private byte[] convertPrivateValueToBytes(BigInteger privateValue, + String curveName) throws InvalidKeySpecException { + + /* Validate private key is within valid range (1 <= d < order) */ + if (privateValue.signum() <= 0) { + throw new InvalidKeySpecException( + "Private key value must be positive"); + } + + /* Convert to byte array */ + return validateAndConvertBigIntegerToBytes(privateValue, + "Private key value", curveName); + } + + /** + * Private helper method for generating ECPublicKey from + * X509EncodedKeySpec. + * + * @param keySpec the X509EncodedKeySpec containing the public key + * + * @return the generated ECPublicKey + * + * @throws InvalidKeySpecException if the key specification is invalid + */ + private PublicKey generatePublicFromX509(X509EncodedKeySpec keySpec) + throws InvalidKeySpecException { + + byte[] x509Der = null; + byte[] pubDer = null; + Ecc ecc = null; + + try { + if (keySpec == null) { + throw new InvalidKeySpecException( + "X509EncodedKeySpec cannot be null"); + } + + x509Der = keySpec.getEncoded(); + if (x509Der == null) { + throw new InvalidKeySpecException( + "X509EncodedKeySpec contains null encoded key"); + } + + log("decoding X509 public key, length: " + x509Der.length); + + /* Import X509 key into Ecc, validates DER */ + ecc = new Ecc(); + ecc.publicKeyDecode(x509Der); + + /* Export as X509 to get wolfCrypt DER format */ + pubDer = ecc.publicKeyEncode(); + if (pubDer == null) { + throw new InvalidKeySpecException( + "Failed to export public key as DER from Ecc object"); + } + + /* Create wolfJCE ECPublicKey object */ + return new WolfCryptECPublicKey(pubDer); + + } catch (WolfCryptException e) { + throw new InvalidKeySpecException( + "wolfCrypt error during X509 key decode: " + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + if (pubDer != null) { + Arrays.fill(pubDer, (byte)0); + } + } + } + + /** + * Private helper method for generating ECPublicKey from + * ECPublicKeySpec. + * + * @param keySpec the ECPublicKeySpec containing the public key + * + * @return the generated ECPublicKey + * + * @throws InvalidKeySpecException if the key specification is invalid + */ + private PublicKey generatePublicFromECSpec(ECPublicKeySpec keySpec) + throws InvalidKeySpecException { + + Ecc ecc = null; + byte[] derData = null; + byte[] x = null; + byte[] y = null; + String curveName = null; + + try { + log("generating ECPublicKey from ECPublicKeySpec"); + + if (keySpec == null) { + throw new InvalidKeySpecException( + "ECPublicKeySpec cannot be null"); + } + + if (keySpec.getW() == null) { + throw new InvalidKeySpecException( + "Public key point cannot be null"); + } + if (keySpec.getParams() == null) { + throw new InvalidKeySpecException( + "ECParameterSpec cannot be null"); + } + + /* Validate ECParameterSpec is supported by wolfCrypt, + * throws WolfCryptException if invalid */ + WolfCryptECParameterSpec.validateParameters(keySpec.getParams()); + + /* Get curve name from ECParameterSpec */ + try { + curveName = WolfCryptECParameterSpec.getCurveName( + keySpec.getParams()); + + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidKeySpecException( + "Unsupported curve parameters: " + e.getMessage(), e); + } + if (curveName == null || curveName.isEmpty()) { + throw new InvalidKeySpecException( + "Unable to determine curve name from ECParameterSpec"); + } + + /* Convert ECPoint to {x, y} coordinate byte arrays */ + x = validateAndConvertBigIntegerToBytes( + keySpec.getW().getAffineX(), "X coordinate", curveName); + y = validateAndConvertBigIntegerToBytes( + keySpec.getW().getAffineY(), "Y coordinate", curveName); + + /* Import public key {x, y} into Ecc object */ + ecc = new Ecc(); + ecc.importPublicRaw(x, y, curveName); + + /* Export as DER encoded format */ + derData = ecc.publicKeyEncode(); + if (derData == null) { + throw new InvalidKeySpecException( + "Failed to export public key DER"); + } + + /* Create wolfJCE ECPublicKey object */ + return new WolfCryptECPublicKey(derData); + + } catch (WolfCryptException e) { + throw new InvalidKeySpecException( + "wolfCrypt error during ECPublicKeySpec conversion: " + + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + if (derData != null) { + Arrays.fill(derData, (byte)0); + } + if (x != null) { + Arrays.fill(x, (byte)0); + } + if (y != null) { + Arrays.fill(y, (byte)0); + } + } + } + + + /** + * Private helper methods for extracting KeySpec from Key. + * + * @param the type of KeySpec to be returned + * @param key the ECPrivateKey from which to extract the KeySpec + * @param keySpec the class object of the requested KeySpec type + * + * @return the extracted KeySpec of the requested type + * + * @throws InvalidKeySpecException if the KeySpec type is unsupported + */ + @SuppressWarnings("unchecked") + private T getPrivateKeySpec(ECPrivateKey key, + Class keySpec) throws InvalidKeySpecException { + + try { + if (key == null) { + throw new InvalidKeySpecException( + "ECPrivateKey cannot be null"); + } + if (keySpec == null) { + throw new InvalidKeySpecException( + "Requested KeySpec format cannot be null"); + } + + log("extracting private key spec of type: " + keySpec.getName()); + + if (keySpec.isAssignableFrom(PKCS8EncodedKeySpec.class)) { + byte[] encoded = key.getEncoded(); + if (encoded == null) { + throw new InvalidKeySpecException( + "ECPrivateKey.getEncoded() returned null"); + } + return (T) new PKCS8EncodedKeySpec(encoded); + } + else if (keySpec.isAssignableFrom(ECPrivateKeySpec.class)) { + /* Extract private value and params directly from key */ + return (T) new ECPrivateKeySpec(key.getS(), key.getParams()); + } + else { + throw new InvalidKeySpecException( + "Unsupported KeySpec type: " + keySpec.getName()); + } + + } catch (Exception e) { + throw new InvalidKeySpecException( + "Failed to extract private key spec: " + e.getMessage(), e); + } + } + + /** + * Private helper methods for extracting KeySpec from Key. + * + * @param the type of KeySpec to be returned + * @param key the ECPublicKey from which to extract the KeySpec + * @param keySpec the class object of the requested KeySpec type + * + * @return the extracted KeySpec of the requested type + * + * @throws InvalidKeySpecException if the KeySpec type is unsupported + */ + @SuppressWarnings("unchecked") + private T getPublicKeySpec(ECPublicKey key, + Class keySpec) throws InvalidKeySpecException { + + try { + if (key == null) { + throw new InvalidKeySpecException( + "ECPublicKey cannot be null"); + } + if (keySpec == null) { + throw new InvalidKeySpecException( + "Requested KeySpec format cannot be null"); + } + + log("extracting public key spec of type: " + keySpec.getName()); + + if (keySpec.isAssignableFrom(X509EncodedKeySpec.class)) { + byte[] encoded = key.getEncoded(); + if (encoded == null) { + throw new InvalidKeySpecException( + "ECPublicKey.getEncoded() returned null"); + } + return (T) new X509EncodedKeySpec(encoded); + } + else if (keySpec.isAssignableFrom(ECPublicKeySpec.class)) { + /* Extract public point and parameters directly from key */ + return (T) new ECPublicKeySpec(key.getW(), key.getParams()); + } + else { + throw new InvalidKeySpecException( + "Unsupported KeySpec type: " + keySpec.getName()); + } + + } catch (Exception e) { + throw new InvalidKeySpecException( + "Failed to extract public key spec: " + e.getMessage(), e); + } + } + + /** + * Translate ECPrivateKey from foreign provider into wolfJCE ECPrivateKey. + * + * @param key the ECPrivateKey to be translated + * + * @return the translated PrivateKey + * + * @throws InvalidKeyException if the key cannot be translated + */ + private PrivateKey translatePrivateKey(ECPrivateKey key) + throws InvalidKeyException { + + try { + log("translating ECPrivateKey from foreign provider"); + + if (key == null) { + throw new InvalidKeyException( + "ECPrivateKey cannot be null"); + } + + /* Get encoded format and convert through our KeyFactory */ + byte[] encoded = key.getEncoded(); + if (encoded == null) { + throw new InvalidKeyException( + "ECPrivateKey.getEncoded() returned null"); + } + + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + return engineGeneratePrivate(keySpec); + + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to translate ECPrivateKey: " + e.getMessage(), e); + } + } + + /** + * Translate ECPublicKey from foreign provider into wolfJCE ECPublicKey. + * + * @param key the ECPublicKey to be translated + * + * @return the translated PublicKey + * + * @throws InvalidKeyException if the key cannot be translated + */ + private PublicKey translatePublicKey(ECPublicKey key) + throws InvalidKeyException { + + try { + log("translating ECPublicKey from foreign provider"); + + if (key == null) { + throw new InvalidKeyException( + "ECPublicKey cannot be null"); + } + + /* Get encoded format and convert through our KeyFactory */ + byte[] encoded = key.getEncoded(); + if (encoded == null) { + throw new InvalidKeyException( + "ECPublicKey.getEncoded() returned null"); + } + + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); + return engineGeneratePublic(keySpec); + + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to translate ECPublicKey: " + e.getMessage(), e); + } + } +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptECParameterSpec.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptECParameterSpec.java new file mode 100644 index 00000000..e9c4d72f --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptECParameterSpec.java @@ -0,0 +1,565 @@ +/* WolfCryptECParameterSpec.java + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +package com.wolfssl.provider.jce; + +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.security.InvalidAlgorithmParameterException; +import java.security.spec.ECPoint; +import java.security.spec.EllipticCurve; +import java.security.spec.ECFieldFp; + +import com.wolfssl.wolfcrypt.Ecc; +import com.wolfssl.wolfcrypt.WolfCryptException; + +/** + * wolfJCE ECParameterSpec implementation. + * + * This class extends ECParameterSpec to store curve name metadata. + * It also provides static helper methods for working with ECParameterSpec + * and integrating with system AlgorithmParameters when needed. + */ +public class WolfCryptECParameterSpec extends ECParameterSpec { + + /* Stored ECC curve name */ + private final String curveName; + + /** + * Create ECParameterSpec with curve name. + * + * @param curve the elliptic curve + * @param generator the generator point + * @param order the order of the generator point + * @param cofactor the cofactor + * @param curveName the wolfCrypt curve name + */ + public WolfCryptECParameterSpec(EllipticCurve curve, ECPoint generator, + BigInteger order, int cofactor, String curveName) { + + super(curve, generator, order, cofactor); + this.curveName = curveName; + } + + /** + * Get the stored curve name without needing to do parameter matching. + * + * @return curve name + */ + public String getStoredCurveName() { + return curveName; + } + + /** + * Compares this ECParameterSpec for equality with another object. + * Two ECParameterSpec objects are equal if they have the same curve, + * generator point, order, cofactor, and stored curve name (if both + * are WolfCryptECParameterSpec instances). + * + * @param obj the object to compare with + * + * @return true if the objects are equal, false otherwise + */ + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + if (!(obj instanceof ECParameterSpec)) { + return false; + } + + ECParameterSpec other = (ECParameterSpec) obj; + + /* Compare all ECParameterSpec fields */ + if (!getCurve().equals(other.getCurve())) { + return false; + } + if (!getGenerator().equals(other.getGenerator())) { + return false; + } + if (!getOrder().equals(other.getOrder())) { + return false; + } + if (getCofactor() != other.getCofactor()) { + return false; + } + + /* If both are WolfCryptECParameterSpec, compare stored curve names */ + if (obj instanceof WolfCryptECParameterSpec) { + WolfCryptECParameterSpec otherWolf = (WolfCryptECParameterSpec) obj; + if (curveName != null) { + return curveName.equals(otherWolf.curveName); + } else { + return otherWolf.curveName == null; + } + } + + return true; + } + + /** + * Returns a hash code for this ECParameterSpec. + * The hash code is computed based on the curve, generator, order, + * cofactor, and stored curve name. + * + * @return hash code for this ECParameterSpec + */ + @Override + public int hashCode() { + + int result = getCurve().hashCode(); + + /* Use 31 as a multiplier for hash code combination, following + * how Java hashCode() does. (31 is an odd prime) */ + result = 31 * result + getGenerator().hashCode(); + result = 31 * result + getOrder().hashCode(); + result = 31 * result + getCofactor(); + + if (curveName != null) { + result = 31 * result + curveName.hashCode(); + } + return result; + } + + /** + * Returns a string representation of this ECParameterSpec. + * Includes the stored curve name for easier debugging. + * + * @return string representation of this ECParameterSpec + */ + @Override + public String toString() { + return "WolfCryptECParameterSpec{" + + "curveName='" + curveName + '\'' + + ", fieldSize=" + getCurve().getField().getFieldSize() + " bits" + + ", cofactor=" + getCofactor() + '}'; + } + + /** + * Extract ECParameterSpec from DER-encoded algorithm identifier. + * + * @param algoIDDer DER-encoded algorithm identifier + * + * @return ECParameterSpec parsed from the algorithm identifier + * + * @throws IllegalArgumentException if parsing fails + */ + public static ECParameterSpec parseFromAlgorithmIdentifier( + byte[] algoIDDer) throws IllegalArgumentException { + + if (algoIDDer == null || algoIDDer.length == 0) { + throw new IllegalArgumentException( + "Algorithm identifier DER cannot be null or empty"); + } + + try { + /* Use system AlgorithmParameters to parse EC parameters. + * TODO: switch to wolfJCE EC AlgorithmParameters if/when that + * implementation happens. For now, we just use the system + * AlgorithmParameters since there is no crypto done in that + * class. */ + AlgorithmParameters algParams = + AlgorithmParameters.getInstance("EC"); + algParams.init(algoIDDer); + + return algParams.getParameterSpec(ECParameterSpec.class); + + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException( + "EC AlgorithmParameters not available", e); + + } catch (InvalidParameterSpecException e) { + throw new IllegalArgumentException( + "Invalid EC algorithm identifier", e); + + } catch (Exception e) { + throw new IllegalArgumentException( + "Failed to parse EC algorithm identifier", e); + } + } + + /** + * Extract ECParameterSpec from DER-encoded EC key. + * + * This method uses wolfCrypt to load the key and extract curve parameters. + * + * @param keyDer DER-encoded EC key (PKCS#8 or X.509) + * @param isPrivateKey true if this is a private key, false for public key + * + * @return ECParameterSpec for the key + * + * @throws IllegalArgumentException if parameter extraction fails + */ + public static ECParameterSpec extractFromKey(byte[] keyDer, + boolean isPrivateKey) throws IllegalArgumentException { + + Ecc ecc = null; + + if (keyDer == null || keyDer.length == 0) { + throw new IllegalArgumentException( + "Key DER cannot be null or empty"); + } + + try { + /* Load key into Ecc to access curve information */ + ecc = new Ecc(); + + if (isPrivateKey) { + ecc.privateKeyDecode(keyDer); + } else { + ecc.publicKeyDecode(keyDer); + } + + /* Get curve ID from the key */ + int curveId = ecc.getCurveId(); + String curveName = Ecc.getCurveNameFromId(curveId); + + if (curveName == null) { + throw new IllegalArgumentException( + "Unknown curve ID: " + curveId); + } + + /* Create ECParameterSpec using wolfCrypt Ecc class */ + return createECParameterSpec(curveName); + + } catch (WolfCryptException e) { + throw new IllegalArgumentException( + "wolfCrypt error during key decode: " + e.getMessage(), e); + + } catch (Exception e) { + throw new IllegalArgumentException( + "Failed to extract EC parameters from key: " + + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + } + } + + /** + * Get curve name from ECParameterSpec. + * + * First checks if the ECParameterSpec is ours, with stored curve name. + * If so, returns the stored name without parameter matching. Otherwise, + * falls back to parameter-based identification. + * + * @param paramSpec ECParameterSpec to get curve name from + * + * @return curve name string, or null if not found + * + * @throws InvalidAlgorithmParameterException if parameters are invalid + */ + public static String getCurveName(ECParameterSpec paramSpec) + throws InvalidAlgorithmParameterException { + + if (paramSpec == null) { + return null; + } + + /* Check if this is our ECParameterSpec with stored curve name */ + if (paramSpec instanceof WolfCryptECParameterSpec) { + WolfCryptECParameterSpec enhanced = + (WolfCryptECParameterSpec) paramSpec; + return enhanced.getStoredCurveName(); + } + + /* Fall back to parameter-based identification for external + * ECParameterSpec objects */ + return Ecc.getCurveName(paramSpec); + } + + /** + * Check if ECParameterSpec has stored curve name metadata. + * + * @param paramSpec ECParameterSpec to check + * + * @return true if enhanced with stored curve name, false otherwise + */ + public static boolean hasStoredCurveName(ECParameterSpec paramSpec) { + return paramSpec instanceof WolfCryptECParameterSpec; + } + + /** + * Identify ECC curve name by comparing ECParameterSpec parameters against + * all curves supported by wolfCrypt. + * + * @param paramSpec ECParameterSpec to identify + * + * @return curve name string, or null if no match found + */ + private static String identifyCurveByParameters(ECParameterSpec spec) { + + int targetCofactor; + int cofactor; + + if (spec == null) { + return null; + } + + try { + /* Get all supported curves from wolfCrypt */ + String[] supportedCurves = Ecc.getAllSupportedCurves(); + + /* Extract parameters from the input ECParameterSpec */ + EllipticCurve curve = spec.getCurve(); + if (!(curve.getField() instanceof ECFieldFp)) { + return null; /* Only support prime fields */ + } + + ECFieldFp field = (ECFieldFp) curve.getField(); + BigInteger targetP = field.getP(); + BigInteger targetA = curve.getA(); + BigInteger targetB = curve.getB(); + BigInteger targetN = spec.getOrder(); + ECPoint targetG = spec.getGenerator(); + targetCofactor = spec.getCofactor(); + + /* Compare against each supported curve */ + for (String curveName : supportedCurves) { + try { + String[] curveParams = Ecc.getCurveParameters(curveName); + + /* Parse wolfCrypt curve parameters */ + BigInteger p = new BigInteger(curveParams[0], 16); + + /* Field size check for early exit on curve mismatch */ + if (targetP.bitLength() != p.bitLength()) { + continue; + } + + /* Prime field check for early exit on curve mismatch */ + if (!targetP.equals(p)) { + continue; + } + + BigInteger a = new BigInteger(curveParams[1], 16); + BigInteger b = new BigInteger(curveParams[2], 16); + BigInteger n = new BigInteger(curveParams[3], 16); + + cofactor = Integer.parseInt(curveParams[6]); + if (targetCofactor != cofactor) { + continue; + } + + /* Compare a, b, n, and generator */ + if (targetA.equals(a) && targetB.equals(b) && + targetN.equals(n)) { + + BigInteger gx = new BigInteger(curveParams[4], 16); + BigInteger gy = new BigInteger(curveParams[5], 16); + + if (targetG.getAffineX().equals(gx) && + targetG.getAffineY().equals(gy)) { + + log("identified curve by parameter matching: " + + curveName); + return curveName; + } + } + + } catch (Exception e) { + /* Continue to next curve */ + continue; + } + } + + log("no curve match found for ECParameterSpec with field size " + + targetP.bitLength()); + return null; + + } catch (Exception e) { + log("error during curve parameter matching: " + e.getMessage()); + return null; + } + } + + /** + * Validate if given ECParameterSpec is supported by wolfCrypt. + * + * @param spec ECParameterSpec to validate + * + * @throws IllegalArgumentException if parameters are not supported + */ + public static void validateParameters(ECParameterSpec spec) + throws IllegalArgumentException { + + if (spec == null) { + throw new IllegalArgumentException( + "ECParameterSpec cannot be null"); + } + + try { + String curveName = getCurveName(spec); + if (curveName == null) { + throw new IllegalArgumentException( + "ECParameterSpec curve not supported by wolfCrypt"); + } + } catch (InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException( + "ECParameterSpec curve not supported by wolfCrypt: " + + e.getMessage()); + } + } + + /** + * Normalize wolfCrypt curve names to standard format expected by + * system AlgorithmParameters. + * + * @param wolfCryptName curve name from wolfCrypt + * + * @return normalized curve name for system use + */ + private static String normalizeStandardCurveName(String wolfCryptName) { + if (wolfCryptName == null) { + return null; + } + + /* Convert wolfCrypt curve names to standard names for common curves. + * For most curves, wolfCrypt uses uppercase names while Java standards + * prefer lowercase. For newer curves like Brainpool, we keep the + * original wolfCrypt naming. */ + switch (wolfCryptName.toUpperCase()) { + case "SECP256R1": + return "secp256r1"; + case "SECP384R1": + return "secp384r1"; + case "SECP521R1": + return "secp521r1"; + case "SECP224R1": + return "secp224r1"; + case "SECP192R1": + return "secp192r1"; + case "SECP256K1": + return "secp256k1"; + case "SECP224K1": + return "secp224k1"; + case "SECP192K1": + return "secp192k1"; + case "SECP160R1": + return "secp160r1"; + case "SECP160R2": + return "secp160r2"; + case "SECP160K1": + return "secp160k1"; + /* For Brainpool, prime curves, and special curves like SAKKE, + * keep the original wolfCrypt naming */ + default: + /* Try lowercase as fallback for consistency */ + return wolfCryptName.toLowerCase(); + } + } + + /** + * Create ECParameterSpec directly from wolfCrypt curve parameters. + * + * @param curveName name of ECC curve + * + * @return newly created ECParameterSpec + * + * @throws IllegalArgumentException if curve is not supported or + * parameter creation fails + */ + public static ECParameterSpec createECParameterSpec(String curveName) + throws IllegalArgumentException { + + if (curveName == null) { + throw new IllegalArgumentException("Curve name cannot be null"); + } + + try { + log("creating ECParameterSpec from wolfCrypt for curve: " + + curveName); + + /* Extract curve parameters directly from wolfCrypt */ + String[] params = Ecc.getCurveParameters(curveName); + + /* Parse parameters from hex strings (radix 16) to BigInteger: + * params[0] = p (field prime) + * params[1] = a (curve coefficient a) + * params[2] = b (curve coefficient b) + * params[3] = n (curve order) + * params[4] = gx (generator x) + * params[5] = gy (generator y) + * params[6] = cofactor (integer) + */ + BigInteger p = new BigInteger(params[0], 16); + BigInteger a = new BigInteger(params[1], 16); + BigInteger b = new BigInteger(params[2], 16); + BigInteger n = new BigInteger(params[3], 16); + BigInteger gx = new BigInteger(params[4], 16); + BigInteger gy = new BigInteger(params[5], 16); + int cofactor = Integer.parseInt(params[6]); + + /* Create EC field (prime field) */ + ECFieldFp field = new ECFieldFp(p); + + /* Create elliptic curve */ + EllipticCurve curve = new EllipticCurve(field, a, b); + + /* Create generator point */ + ECPoint generator = new ECPoint(gx, gy); + + /* Create WolfCryptECParameterSpec with stored curve name */ + WolfCryptECParameterSpec paramSpec = + new WolfCryptECParameterSpec(curve, generator, n, cofactor, + curveName); + + log("successfully created ECParameterSpec for " + curveName + + " with field size " + p.bitLength() + " (curve name stored)"); + + return paramSpec; + + } catch (WolfCryptException e) { + throw new IllegalArgumentException( + "wolfCrypt error extracting curve parameters: " + + e.getMessage(), e); + + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Invalid curve parameter format: " + e.getMessage(), e); + + } catch (Exception e) { + throw new IllegalArgumentException( + "Failed to create ECParameterSpec from wolfCrypt: " + + e.getMessage(), e); + } + } + + /** + * Internal method for logging output. + * + * @param msg message to be logged + */ + private static void log(String msg) { + WolfCryptDebug.log(WolfCryptECParameterSpec.class, WolfCryptDebug.INFO, + () -> "[WolfCryptECParameterSpec] " + msg); + } +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptECPrivateKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptECPrivateKey.java new file mode 100644 index 00000000..8e288369 --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptECPrivateKey.java @@ -0,0 +1,410 @@ +/* WolfCryptECPrivateKey.java + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +package com.wolfssl.provider.jce; + +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; + +import com.wolfssl.wolfcrypt.Ecc; +import com.wolfssl.wolfcrypt.WolfCryptException; + +/** + * wolfJCE ECPrivateKey implementation. + * This class implements the ECPrivateKey interface using wolfCrypt. + */ +public class WolfCryptECPrivateKey implements ECPrivateKey { + + private static final long serialVersionUID = 1L; + + /** DER-encoded private key (PKCS#8 format) */ + private byte[] encoded = null; + + /** Cached ECParameterSpec, extracted on first access */ + private transient ECParameterSpec paramSpec = null; + + /** Cached private key value, extracted on first access */ + private transient BigInteger privateValue = null; + + /** Track if object has been destroyed */ + private boolean destroyed = false; + + /** Lock around use of destroyed boolean and cached values */ + private transient final Object stateLock = new Object(); + + /** + * Create new WolfCryptECPrivateKey from DER-encoded PKCS#8 data. + * + * @param encoded DER-encoded PKCS#8 private key + * + * @throws IllegalArgumentException if encoded data is null or invalid + */ + public WolfCryptECPrivateKey(byte[] encoded) + throws IllegalArgumentException { + + if (encoded == null || encoded.length == 0) { + throw new IllegalArgumentException( + "Encoded key data cannot be null or empty"); + } + + /* Validate DER format by importing into wolfCrypt */ + validateDerFormat(encoded); + + /* Store a copy of the encoded data */ + this.encoded = encoded.clone(); + } + + /** + * Create new WolfCryptECPrivateKey from private value and parameters. + * + * @param privateValue the private key value + * @param paramSpec the EC parameters + * + * @throws IllegalArgumentException if parameters are invalid + */ + public WolfCryptECPrivateKey(BigInteger privateValue, + ECParameterSpec paramSpec) throws IllegalArgumentException { + + if (privateValue == null) { + throw new IllegalArgumentException( + "Private value cannot be null"); + } + if (paramSpec == null) { + throw new IllegalArgumentException( + "ECParameterSpec cannot be null"); + } + + /* Store params */ + this.privateValue = privateValue; + this.paramSpec = paramSpec; + + /* Generate DER-encoded form */ + this.encoded = generateDerFromParameters(); + } + + /** + * Validate DER format by attempting to import into wolfCrypt Ecc. + * + * @param derData DER-encoded key data to validate + * + * @throws IllegalArgumentException if DER data is invalid + */ + private void validateDerFormat(byte[] derData) + throws IllegalArgumentException { + + Ecc ecc = null; + + try { + ecc = new Ecc(); + ecc.privateKeyDecode(derData); + + } catch (WolfCryptException e) { + throw new IllegalArgumentException( + "Invalid DER-encoded private key: " + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + } + } + + /** + * Generate DER-encoded form from private value and parameters. + * + * @return DER-encoded PKCS#8 private key + * + * @throws IllegalArgumentException if key generation fails + */ + private byte[] generateDerFromParameters() + throws IllegalArgumentException { + + byte[] privKeyBytes = null; + Ecc ecc = null; + String curveName = null; + + try { + + log("generating DER from private value and ECParameterSpec"); + + /* Get curve name from the ECParameterSpec */ + try { + curveName = + WolfCryptECParameterSpec.getCurveName(this.paramSpec); + + } catch (InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException( + "Unsupported curve in ECParameterSpec: " + e.getMessage()); + } + if (curveName == null) { + throw new IllegalArgumentException( + "Unsupported curve in ECParameterSpec"); + } + + /* Convert private value to byte array */ + privKeyBytes = + Ecc.bigIntToByteArrayNoLeadingZeros(this.privateValue); + + /* Import private key into Ecc using raw import */ + ecc = new Ecc(); + ecc.importPrivateRaw(privKeyBytes, curveName); + + /* Export as PKCS#8 DER */ + byte[] derEncoded = ecc.privateKeyEncodePKCS8(); + if (derEncoded == null) { + throw new IllegalArgumentException( + "Failed to encode private key as DER"); + } + + log("successfully generated DER from raw parameters, length: " + + derEncoded.length); + + return derEncoded; + + } catch (WolfCryptException e) { + throw new IllegalArgumentException( + "Failed to generate DER from parameters: " + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + if (privKeyBytes != null) { + Arrays.fill(privKeyBytes, (byte) 0); + } + } + } + + /** + * Extract ECParameterSpec from the DER-encoded key. + * + * @return ECParameterSpec for this key + * + * @throws IllegalStateException if parameter extraction fails + */ + private ECParameterSpec extractECParameterSpec() + throws IllegalStateException { + + log("extracting ECParameterSpec from DER-encoded private key"); + + return WolfCryptECParameterSpec.extractFromKey(this.encoded, true); + } + + /** + * Extract private key value from the DER-encoded key. + * + * @return BigInteger representing the private key value + * + * @throws IllegalStateException if private value extraction fails + */ + private BigInteger extractPrivateValue() throws IllegalStateException { + + byte[] privKeyBytes = null; + Ecc ecc = null; + + try { + /* Load the private key into wolfCrypt and export raw private key */ + ecc = new Ecc(); + ecc.privateKeyDecode(this.encoded); + + /* Export the raw private key scalar */ + privKeyBytes = ecc.exportPrivateRaw(); + if ((privKeyBytes == null) || (privKeyBytes.length == 0)) { + throw new IllegalStateException("Failed to export private key"); + } + + /* Convert to BigInteger (unsigned, positive) */ + return new BigInteger(1, privKeyBytes); + + } catch (WolfCryptException e) { + throw new IllegalStateException( + "Failed to extract private value: " + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + if (privKeyBytes != null) { + Arrays.fill(privKeyBytes, (byte) 0); + } + } + } + + /** + * Internal method for logging output. + * + * @param msg message to be logged + */ + private void log(String msg) { + WolfCryptDebug.log(getClass(), WolfCryptDebug.INFO, + () -> "[WolfCryptECPrivateKey] " + msg); + } + + @Override + public BigInteger getS() { + synchronized (stateLock) { + if (destroyed) { + throw new IllegalStateException("Key has been destroyed"); + } + + if (privateValue == null) { + log("extracting private key value from DER"); + privateValue = extractPrivateValue(); + } + return privateValue; + } + } + + @Override + public ECParameterSpec getParams() { + synchronized (stateLock) { + if (destroyed) { + throw new IllegalStateException("Key has been destroyed"); + } + + if (paramSpec == null) { + log("extracting EC parameters from DER"); + paramSpec = extractECParameterSpec(); + } + return paramSpec; + } + } + + @Override + public String getAlgorithm() { + return "EC"; + } + + @Override + public String getFormat() { + return "PKCS#8"; + } + + @Override + public byte[] getEncoded() { + synchronized (stateLock) { + if (destroyed) { + return null; + } + return encoded.clone(); + } + } + + /** + * Destroy this key by zeroing out sensitive data. + */ + public void destroy() { + synchronized (stateLock) { + if (!destroyed) { + if (encoded != null) { + Arrays.fill(encoded, (byte) 0); + } + privateValue = null; + paramSpec = null; + destroyed = true; + log("key destroyed"); + } + } + } + + /** + * Check if this key has been destroyed. + * + * @return true if key has been destroyed + */ + public boolean isDestroyed() { + synchronized (stateLock) { + return destroyed; + } + } + + @Override + public int hashCode() { + synchronized (stateLock) { + if (destroyed) { + return 0; + } + return Arrays.hashCode(encoded); + } + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + if (!(obj instanceof ECPrivateKey)) { + return false; + } + + ECPrivateKey other = (ECPrivateKey) obj; + + synchronized (stateLock) { + if (destroyed) { + return false; + } + + /* Compare encoded forms if both are WolfCryptECPrivateKey */ + if (obj instanceof WolfCryptECPrivateKey) { + WolfCryptECPrivateKey otherWolf = (WolfCryptECPrivateKey) obj; + synchronized (otherWolf.stateLock) { + if (otherWolf.destroyed) { + return false; + } + return Arrays.equals(this.encoded, otherWolf.encoded); + } + } + + /* Compare with other ECPrivateKey implementations */ + try { + if (getS().equals(other.getS()) && + getParams().equals(other.getParams())) { + return true; + } else { + return false; + } + + } catch (Exception e) { + return false; + } + } + } + + @Override + public String toString() { + synchronized (stateLock) { + if (destroyed) { + return "WolfCryptECPrivateKey[DESTROYED]"; + } + return "WolfCryptECPrivateKey[algorithm=EC, format=PKCS#8, " + + "encoded.length=" + encoded.length + "]"; + } + } +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptECPublicKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptECPublicKey.java new file mode 100644 index 00000000..771a9d72 --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptECPublicKey.java @@ -0,0 +1,438 @@ +/* WolfCryptECPublicKey.java + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +package com.wolfssl.provider.jce; + +import java.util.Arrays; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.InvalidAlgorithmParameterException; + +import com.wolfssl.wolfcrypt.Ecc; +import com.wolfssl.wolfcrypt.WolfCryptException; + +/** + * wolfJCE ECPublicKey implementation. + * This class implements the ECPublicKey interface using wolfCrypt. + */ +public class WolfCryptECPublicKey implements ECPublicKey { + + private static final long serialVersionUID = 1L; + + /** DER-encoded public key (X.509 format) */ + private byte[] encoded = null; + + /** Cached ECParameterSpec, extracted on first access */ + private transient ECParameterSpec paramSpec = null; + + /** Cached public key point, extracted on first access */ + private transient ECPoint publicPoint = null; + + /** Track if object has been destroyed */ + private boolean destroyed = false; + + /** Lock around use of destroyed boolean and cached values */ + private transient final Object stateLock = new Object(); + + /** + * Create new WolfCryptECPublicKey from DER-encoded X.509 data. + * + * @param encoded DER-encoded X.509 public key + * + * @throws IllegalArgumentException if encoded data is null or invalid + */ + public WolfCryptECPublicKey(byte[] encoded) + throws IllegalArgumentException { + + if (encoded == null || encoded.length == 0) { + throw new IllegalArgumentException( + "Encoded key data cannot be null or empty"); + } + + /* Validate DER format by importing into wolfCrypt */ + validateDerFormat(encoded); + + /* Store a copy of the encoded data */ + this.encoded = encoded.clone(); + } + + /** + * Create new WolfCryptECPublicKey from public point and parameters. + * + * @param publicPoint the public key point + * @param paramSpec the EC parameters + * + * @throws IllegalArgumentException if parameters are invalid + */ + public WolfCryptECPublicKey(ECPoint publicPoint, + ECParameterSpec paramSpec) throws IllegalArgumentException { + + if (publicPoint == null) { + throw new IllegalArgumentException( + "Public point cannot be null"); + } + if (paramSpec == null) { + throw new IllegalArgumentException( + "ECParameterSpec cannot be null"); + } + + /* Store params */ + this.publicPoint = publicPoint; + this.paramSpec = paramSpec; + + /* Generate the DER-encoded form */ + this.encoded = generateDerFromParameters(); + } + + /** + * Validate DER format by attempting to import into wolfCrypt Ecc. + * + * @param derData DER-encoded key data to validate + * + * @throws IllegalArgumentException if DER data is invalid + */ + private void validateDerFormat(byte[] derData) + throws IllegalArgumentException { + + Ecc ecc = null; + + try { + ecc = new Ecc(); + ecc.publicKeyDecode(derData); + + } catch (WolfCryptException e) { + throw new IllegalArgumentException( + "Invalid DER-encoded public key: " + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + } + } + + /** + * Generate DER-encoded form from public point and parameters. + * + * @return DER-encoded X.509 public key + * + * @throws IllegalArgumentException if key generation fails + */ + private byte[] generateDerFromParameters() + throws IllegalArgumentException { + + byte[] xBytes = null; + byte[] yBytes = null; + Ecc ecc = null; + + try { + log("generating DER from public point and ECParameterSpec"); + + /* Get curve name from the ECParameterSpec */ + String curveName = null; + try { + curveName = + WolfCryptECParameterSpec.getCurveName(this.paramSpec); + + } catch (InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException( + "Unsupported curve in ECParameterSpec: " + e.getMessage()); + } + if (curveName == null) { + throw new IllegalArgumentException( + "Unsupported curve in ECParameterSpec"); + } + + /* Extract X and Y coordinates from the ECPoint */ + BigInteger x = this.publicPoint.getAffineX(); + BigInteger y = this.publicPoint.getAffineY(); + + if (x == null || y == null) { + throw new IllegalArgumentException( + "ECPoint must have affine coordinates"); + } + + /* Convert coordinates to byte[] */ + xBytes = Ecc.bigIntToByteArrayNoLeadingZeros(x); + yBytes = Ecc.bigIntToByteArrayNoLeadingZeros(y); + + /* Import public key into Ecc using raw import */ + ecc = new Ecc(); + ecc.importPublicRaw(xBytes, yBytes, curveName); + + /* Export as X.509 DER */ + byte[] derEncoded = ecc.publicKeyEncode(); + if (derEncoded == null) { + throw new IllegalArgumentException( + "Failed to encode public key as DER"); + } + + log("successfully generated DER from raw parameters, length: " + + derEncoded.length); + + return derEncoded; + + } catch (WolfCryptException e) { + throw new IllegalArgumentException( + "Failed to generate DER from parameters: " + + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + if (xBytes != null) { + Arrays.fill(xBytes, (byte) 0); + } + if (yBytes != null) { + Arrays.fill(yBytes, (byte) 0); + } + } + } + + /** + * Extract ECParameterSpec from the DER-encoded key. + * + * @return ECParameterSpec for this key + * + * @throws IllegalStateException if parameter extraction fails + */ + private ECParameterSpec extractECParameterSpec() + throws IllegalStateException { + + log("extracting ECParameterSpec from DER-encoded public key"); + + return WolfCryptECParameterSpec.extractFromKey(this.encoded, false); + } + + /** + * Extract public key point from the DER-encoded key. + * + * @return ECPoint representing the public key point + * + * @throws IllegalStateException if public point extraction fails + */ + private ECPoint extractPublicPoint() throws IllegalStateException { + + byte[] xBytes = null; + byte[] yBytes = null; + Ecc ecc = null; + + try { + /* Load public key into wolfCrypt */ + ecc = new Ecc(); + ecc.publicKeyDecode(this.encoded); + + /* Export the raw public key coordinates */ + byte[][] coords = ecc.exportPublicRaw(); + if (coords == null || coords.length != 2) { + throw new IllegalStateException( + "Failed to export public key coordinates"); + } + + xBytes = coords[0]; + yBytes = coords[1]; + + if (xBytes == null || yBytes == null) { + throw new IllegalStateException("Invalid coordinate data"); + } + + /* Convert to BigInteger and create ECPoint */ + BigInteger x = new BigInteger(1, xBytes); + BigInteger y = new BigInteger(1, yBytes); + + return new ECPoint(x, y); + + } catch (WolfCryptException e) { + throw new IllegalStateException( + "Failed to extract public point: " + e.getMessage(), e); + + } finally { + if (ecc != null) { + ecc.releaseNativeStruct(); + } + if (xBytes != null) { + Arrays.fill(xBytes, (byte) 0); + } + if (yBytes != null) { + Arrays.fill(yBytes, (byte) 0); + } + } + } + + /** + * Internal method for logging output. + * + * @param msg message to be logged + */ + private void log(String msg) { + WolfCryptDebug.log(getClass(), WolfCryptDebug.INFO, + () -> "[WolfCryptECPublicKey] " + msg); + } + + @Override + public ECPoint getW() { + synchronized (stateLock) { + if (destroyed) { + throw new IllegalStateException("Key has been destroyed"); + } + + if (publicPoint == null) { + log("extracting public key point from DER"); + publicPoint = extractPublicPoint(); + } + + return publicPoint; + } + } + + @Override + public ECParameterSpec getParams() { + synchronized (stateLock) { + if (destroyed) { + throw new IllegalStateException("Key has been destroyed"); + } + + if (paramSpec == null) { + log("extracting EC parameters from DER"); + paramSpec = extractECParameterSpec(); + } + + return paramSpec; + } + } + + @Override + public String getAlgorithm() { + return "EC"; + } + + @Override + public String getFormat() { + return "X.509"; + } + + @Override + public byte[] getEncoded() { + synchronized (stateLock) { + if (destroyed) { + return null; + } + return encoded.clone(); + } + } + + /** + * Destroy this key by zeroing out sensitive data. + */ + public void destroy() { + synchronized (stateLock) { + if (!destroyed) { + if (encoded != null) { + Arrays.fill(encoded, (byte) 0); + } + publicPoint = null; + paramSpec = null; + destroyed = true; + log("key destroyed"); + } + } + } + + /** + * Check if this key has been destroyed. + * + * @return true if key has been destroyed + */ + public boolean isDestroyed() { + synchronized (stateLock) { + return destroyed; + } + } + + @Override + public int hashCode() { + synchronized (stateLock) { + if (destroyed) { + return 0; + } + return Arrays.hashCode(encoded); + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ECPublicKey)) { + return false; + } + + ECPublicKey other = (ECPublicKey) obj; + + synchronized (stateLock) { + if (destroyed) { + return false; + } + + /* Compare encoded forms if both are WolfCryptECPublicKey */ + if (obj instanceof WolfCryptECPublicKey) { + WolfCryptECPublicKey otherWolf = (WolfCryptECPublicKey) obj; + synchronized (otherWolf.stateLock) { + if (otherWolf.destroyed) { + return false; + } + return Arrays.equals(this.encoded, otherWolf.encoded); + } + } + + /* Compare with other ECPublicKey implementations */ + try { + if (getW().equals(other.getW()) && + getParams().equals(other.getParams())) { + return true; + } else { + return false; + } + + } catch (Exception e) { + return false; + } + } + } + + @Override + public String toString() { + synchronized (stateLock) { + if (destroyed) { + return "WolfCryptECPublicKey[DESTROYED]"; + } + return "WolfCryptECPublicKey[algorithm=EC, format=X.509, " + + "encoded.length=" + encoded.length + "]"; + } + } +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java index 5709c724..8afa9de3 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java @@ -442,7 +442,7 @@ private void getCurveFromSpec(AlgorithmParameterSpec spec) ECParameterSpec espec = (ECParameterSpec)spec; - this.curveName = Ecc.getCurveName(espec); + this.curveName = WolfCryptECParameterSpec.getCurveName(espec); this.curveSize = Ecc.getCurveSizeFromName(this.curveName); log("curveName: " + curveName + ", curveSize: " + curveSize); diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java index a7fde2f5..d5098330 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java @@ -454,6 +454,12 @@ private void registerServices() { } } + /* KeyFactory */ + if (FeatureDetect.EccEnabled()) { + put("KeyFactory.EC", + "com.wolfssl.provider.jce.WolfCryptECKeyFactory"); + } + /* KeyStore */ put("KeyStore.WKS", "com.wolfssl.provider.jce.WolfSSLKeyStore"); diff --git a/src/main/java/com/wolfssl/wolfcrypt/Ecc.java b/src/main/java/com/wolfssl/wolfcrypt/Ecc.java index 8e7fe10d..d09ba325 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Ecc.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Ecc.java @@ -21,6 +21,7 @@ package com.wolfssl.wolfcrypt; +import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.spec.EllipticCurve; import java.security.spec.ECParameterSpec; @@ -136,6 +137,16 @@ private native void wc_ecc_import_private(byte[] privKey, byte[] x963Key, private static native int wc_ecc_get_curve_id_from_params(int fieldSize, byte[] prime, byte[] Af, byte[] Bf, byte[] order, byte[] Gx, byte[] Gy, int cofactor); + private static native String[] wc_ecc_get_curve_params_from_name( + String curveName); + private static native String[] wc_ecc_get_all_curve_names(); + private native byte[] wc_ecc_export_private_raw(); + private native byte[][] wc_ecc_export_public_raw(); + private native void wc_ecc_import_private_raw(byte[] privateKey, + String curveName); + private native void wc_ecc_import_public_raw(byte[] xCoord, byte[] yCoord, + String curveName); + private native int wc_ecc_get_curve_id(); /** * Internal helper method to initialize object if/when needed. @@ -598,6 +609,34 @@ public synchronized byte[] privateKeyEncodePKCS8() } } + /** + * Converts a BigInteger to a byte array, removing leading zero bytes + * that BigInteger.toByteArray() may add for sign bit representation. + * + * BigInteger.toByteArray() can prepend a zero byte when the most + * significant bit of the number is set, to indicate that the number + * is positive. This utility method removes such leading zeros. + * + * @param value the BigInteger to convert + * @return byte array representation without leading zero bytes + */ + public static byte[] bigIntToByteArrayNoLeadingZeros(BigInteger value) { + if (value == null) { + return null; + } + + byte[] bytes = value.toByteArray(); + + /* Remove leading zero if present (BigInteger may add for sign bit) */ + if ((bytes.length > 1) && (bytes[0] == 0)) { + byte[] result = new byte[bytes.length - 1]; + System.arraycopy(bytes, 1, result, 0, result.length); + return result; + } + + return bytes; + } + /** * Get ECC curve name from ECParameterSpec * @@ -607,7 +646,8 @@ public synchronized byte[] privateKeyEncodePKCS8() * * @throws WolfCryptException if native operation fails * @throws InvalidAlgorithmParameterException if spec.getCurve().getField() - * is not an instance of ECFieldFp + * is not an instance of ECFieldFp, or curve parameters do not + * match a supported ECC curve in native wolfCrypt. */ public static String getCurveName(ECParameterSpec spec) throws WolfCryptException, InvalidAlgorithmParameterException { @@ -622,17 +662,196 @@ public static String getCurveName(ECParameterSpec spec) ECFieldFp field = (ECFieldFp)spec.getCurve().getField(); EllipticCurve curve = spec.getCurve(); + /* Convert BigIntegers to byte arrays for native wolfCrypt. + * Remove extra leading zero bytes that BigInteger.toByteArray() + * might add. */ curve_id = wc_ecc_get_curve_id_from_params( field.getFieldSize(), - field.getP().toByteArray(), - curve.getA().toByteArray(), - curve.getB().toByteArray(), - spec.getOrder().toByteArray(), - spec.getGenerator().getAffineX().toByteArray(), - spec.getGenerator().getAffineY().toByteArray(), + bigIntToByteArrayNoLeadingZeros(field.getP()), + bigIntToByteArrayNoLeadingZeros(curve.getA()), + bigIntToByteArrayNoLeadingZeros(curve.getB()), + bigIntToByteArrayNoLeadingZeros(spec.getOrder()), + bigIntToByteArrayNoLeadingZeros( + spec.getGenerator().getAffineX()), + bigIntToByteArrayNoLeadingZeros( + spec.getGenerator().getAffineY()), spec.getCofactor()); + if (curve_id == -1) { + /* ECC_CURVE_INVALID */ + throw new InvalidAlgorithmParameterException( + "Curve parameters do not match a supported ECC curve: " + spec); + } + return wc_ecc_get_curve_name_from_id(curve_id); } + + /** + * Get ECC curve name from curve ID. + * + * Ecc object does not need to be initialized to call this method. + * + * @param curveId curve ID + * + * @return curve name + * + * @throws WolfCryptException if native operation fails + */ + public static String getCurveNameFromId(int curveId) + throws WolfCryptException { + + return wc_ecc_get_curve_name_from_id(curveId); + } + + /** + * Export ECC raw private key scalar. + * + * @return ECC private key scalar as byte array + * + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if key has not been set, if object + * fails to initialize, or if releaseNativeStruct() has been + * called and object has been released. + */ + public synchronized byte[] exportPrivateRaw() + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + throwIfKeyNotLoaded(); + + synchronized (pointerLock) { + return wc_ecc_export_private_raw(); + } + } + + /** + * Export ECC raw public key coordinates. + * + * @return {x, y} coordinate arrays + * + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if key has not been set, if object + * fails to initialize, or if releaseNativeStruct() has been + * called and object has been released. + */ + public synchronized byte[][] exportPublicRaw() + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + throwIfKeyNotLoaded(); + + synchronized (pointerLock) { + return wc_ecc_export_public_raw(); + } + } + + /** + * Import ECC raw private key scalar on specified curve. + * + * @param privateKey private key scalar as byte array + * @param curveName name of ECC curve + * + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if key has already been set, if object + * fails to initialize, or if releaseNativeStruct() has been + * called and object has been released. + */ + public synchronized void importPrivateRaw(byte[] privateKey, + String curveName) throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + throwIfKeyExists(); + + synchronized (pointerLock) { + wc_ecc_import_private_raw(privateKey, curveName); + } + state = WolfCryptState.READY; + } + + /** + * Import ECC raw public key coordinates on specified curve. + * + * @param x x coordinate as byte array + * @param y y coordinate as byte array + * @param curveName name of ECC curve + * + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if key has already been set, if object + * fails to initialize, or if releaseNativeStruct() has been + * called and object has been released. + */ + public synchronized void importPublicRaw(byte[] x, byte[] y, + String curveName) throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + throwIfKeyExists(); + + synchronized (pointerLock) { + wc_ecc_import_public_raw(x, y, curveName); + } + state = WolfCryptState.READY; + } + + /** + * Get ECC curve ID for the current key. + * + * @return curve ID + * + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if key has not been set, if object + * fails to initialize, or if releaseNativeStruct() has been + * called and object has been released. + */ + public synchronized int getCurveId() + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + throwIfKeyNotLoaded(); + + synchronized (pointerLock) { + return wc_ecc_get_curve_id(); + } + } + + /** + * Get ECC curve parameters for specified curve name. + * + * Returns String array ECC curve parameters in the following order: + * + * [0] prime - field prime as hex string + * [1] a - curve coefficient a as hex string + * [2] b - curve coefficient b as hex string + * [3] order - curve order as hex string + * [4] gx - generator point x coordinate as hex string + * [5] gy - generator point y coordinate as hex string + * [6] cofactor - cofactor as decimal string + * + * @param curveName name of ECC curve + * + * @return String array containing curve parameters + * + * @throws WolfCryptException if curve is not supported or native + * operation fails + */ + public static String[] getCurveParameters(String curveName) + throws WolfCryptException { + + if (curveName == null) { + throw new IllegalArgumentException("Curve name cannot be null"); + } + + return wc_ecc_get_curve_params_from_name(curveName); + } + + /** + * Get all curve names supported by the native wolfCrypt library. + * + * @return String array containing all available curve names + * + * @throws WolfCryptException if native operation fails + */ + public static String[] getAllSupportedCurves() throws WolfCryptException { + return wc_ecc_get_all_curve_names(); + } } diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptECKeyFactoryTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptECKeyFactoryTest.java new file mode 100644 index 00000000..3961c790 --- /dev/null +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptECKeyFactoryTest.java @@ -0,0 +1,876 @@ +/* WolfCryptECKeyFactoryTest.java + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +package com.wolfssl.provider.jce.test; + +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +import java.util.ArrayList; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.Security; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.wolfssl.provider.jce.WolfCryptProvider; +import com.wolfssl.provider.jce.WolfCryptECParameterSpec; +import com.wolfssl.wolfcrypt.FeatureDetect; +import com.wolfssl.wolfcrypt.Ecc; + +/** + * JUnit4 test cases for WolfCryptECKeyFactory + */ +public class WolfCryptECKeyFactoryTest { + + private static String supportedCurves[] = { + "secp192r1", + "prime192v2", + "prime192v3", + "prime239v1", + "prime239v2", + "prime239v3", + "secp256r1", + + "secp112r1", + "secp112r2", + "secp128r1", + "secp128r2", + "secp160r1", + "secp224r1", + "secp384r1", + "secp521r1", + + "secp160k1", + "secp192k1", + "secp224k1", + "secp256k1", + + "brainpoolp160r1", + "brainpoolp192r1", + "brainpoolp224r1", + "brainpoolp256r1", + "brainpoolp320r1", + "brainpoolp384r1", + "brainpoolp512r1" + }; + + private static ArrayList enabledCurves = + new ArrayList(); + + @Rule(order = Integer.MIN_VALUE) + public TestRule testWatcher = new TestWatcher() { + + protected void starting(Description desc) { + System.out.println("\t" + desc.getMethodName()); + } + }; + + @BeforeClass + public static void testProviderInstallation() { + + /* Install wolfJCE provider for testing */ + Security.insertProviderAt(new WolfCryptProvider(), 1); + + System.out.println("JCE WolfCryptECKeyFactory Class"); + + if (!FeatureDetect.EccEnabled()) { + System.out.println("EC support not compiled in, skipping tests"); + return; + } + + /* Build list of enabled curves and key sizes, + * getCurveSizeFromName() will return 0 if curve not found */ + for (int i = 0; i < supportedCurves.length; i++) { + + int size = Ecc.getCurveSizeFromName( + supportedCurves[i].toUpperCase()); + + if (size > 0) { + /* Also check if the curve supports round-trip + * ECParameterSpec conversion */ + boolean supportsRoundTrip = + testCurveRoundTripSupport(supportedCurves[i]); + + if (supportsRoundTrip) { + enabledCurves.add(supportedCurves[i]); + } else { + System.out.println("Skipping curve: " + supportedCurves[i] + + " (" + size + " bits) - no ECParameterSpec " + + "round-trip support"); + } + } + } + } + + /** + * Test if a curve supports round-trip ECParameterSpec conversion. + * Some curves can generate keys but can't match ECParameterSpec back to + * curve names. + */ + private static boolean testCurveRoundTripSupport(String curveName) { + + try { + /* Generate a key pair */ + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec(curveName)); + KeyPair kp = kpg.generateKeyPair(); + + /* Extract ECParameterSpec */ + ECPrivateKey privKey = (ECPrivateKey) kp.getPrivate(); + ECParameterSpec params = privKey.getParams(); + + /* Test if we can identify the curve from the ECParameterSpec */ + String detectedCurve = + WolfCryptECParameterSpec.getCurveName(params); + return (detectedCurve != null && !detectedCurve.isEmpty()); + + } catch (Exception e) { + /* Any exception means the curve doesn't support round-trip */ + return false; + } + } + + @Test + public void testECKeyFactoryInstantiation() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Test that we can get an EC KeyFactory instance */ + KeyFactory kf = KeyFactory.getInstance("EC", "wolfJCE"); + assertNotNull("KeyFactory should not be null", kf); + assertEquals("Provider should be wolfJCE", "wolfJCE", + kf.getProvider().getName()); + } + + @Test + public void testPKCS8PrivateKeyConversion() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Generate a test key pair using wolfJCE */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = kpg.generateKeyPair(); + + ECPrivateKey privKey = (ECPrivateKey) kp.getPrivate(); + + /* Get the encoded form */ + byte[] encoded = privKey.getEncoded(); + assertNotNull("Encoded key should not be null", encoded); + + /* Convert back using our KeyFactory */ + KeyFactory kf = KeyFactory.getInstance("EC", "wolfJCE"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + PrivateKey convertedKey = kf.generatePrivate(keySpec); + + assertNotNull("Converted key should not be null", convertedKey); + assertTrue("Should be ECPrivateKey", + convertedKey instanceof ECPrivateKey); + + /* Compare the encoded forms */ + byte[] convertedEncoded = convertedKey.getEncoded(); + assertNotNull("Converted encoded key should not be null", + convertedEncoded); + assertArrayEquals("Encoded forms should match", encoded, + convertedEncoded); + } + + @Test + public void testX509PublicKeyConversion() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Generate a test key pair using wolfJCE */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = kpg.generateKeyPair(); + + ECPublicKey pubKey = (ECPublicKey) kp.getPublic(); + + /* Get the encoded form */ + byte[] encoded = pubKey.getEncoded(); + assertNotNull("Encoded key should not be null", encoded); + + /* Convert back using our KeyFactory */ + KeyFactory kf = KeyFactory.getInstance("EC", "wolfJCE"); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); + PublicKey convertedKey = kf.generatePublic(keySpec); + + assertNotNull("Converted key should not be null", convertedKey); + assertTrue("Should be ECPublicKey", + convertedKey instanceof ECPublicKey); + + /* Compare the encoded forms */ + byte[] convertedEncoded = convertedKey.getEncoded(); + assertNotNull("Converted encoded key should not be null", + convertedEncoded); + assertArrayEquals("Encoded forms should match", encoded, + convertedEncoded); + } + + @Test + public void testECPrivateKeySpecConversion() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Generate a test key pair using system provider for reference */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = kpg.generateKeyPair(); + + ECPrivateKey privKey = (ECPrivateKey) kp.getPrivate(); + + /* Extract ECPrivateKeySpec using system KeyFactory */ + KeyFactory sysKF = KeyFactory.getInstance("EC"); + ECPrivateKeySpec keySpec = sysKF.getKeySpec(privKey, + ECPrivateKeySpec.class); + + /* Convert using our KeyFactory */ + KeyFactory wolfKF = KeyFactory.getInstance("EC", "wolfJCE"); + PrivateKey convertedKey = wolfKF.generatePrivate(keySpec); + + assertNotNull("Converted key should not be null", convertedKey); + assertTrue("Should be ECPrivateKey", + convertedKey instanceof ECPrivateKey); + + /* Verify key parameters match */ + ECPrivateKey convertedECKey = (ECPrivateKey) convertedKey; + assertEquals("Private key values should match", + privKey.getS(), convertedECKey.getS()); + } + + @Test + public void testECPublicKeySpecConversion() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Generate a test key pair using system provider for reference */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = kpg.generateKeyPair(); + + ECPublicKey pubKey = (ECPublicKey) kp.getPublic(); + + /* Extract ECPublicKeySpec using system KeyFactory */ + KeyFactory sysKF = KeyFactory.getInstance("EC"); + ECPublicKeySpec keySpec = sysKF.getKeySpec(pubKey, + ECPublicKeySpec.class); + + /* Convert using our KeyFactory */ + KeyFactory wolfKF = KeyFactory.getInstance("EC", "wolfJCE"); + PublicKey convertedKey = wolfKF.generatePublic(keySpec); + + assertNotNull("Converted key should not be null", convertedKey); + assertTrue("Should be ECPublicKey", + convertedKey instanceof ECPublicKey); + + /* Verify key parameters match */ + ECPublicKey convertedECKey = (ECPublicKey) convertedKey; + assertEquals("Public key points should match", + pubKey.getW(), convertedECKey.getW()); + } + + @Test + public void testKeySpecExtraction() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Generate a test key pair using wolfJCE */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = kpg.generateKeyPair(); + + KeyFactory kf = KeyFactory.getInstance("EC", "wolfJCE"); + + /* Test PKCS8EncodedKeySpec extraction */ + PKCS8EncodedKeySpec privSpec = kf.getKeySpec(kp.getPrivate(), + PKCS8EncodedKeySpec.class); + assertNotNull("PKCS8EncodedKeySpec should not be null", privSpec); + assertNotNull("Encoded bytes should not be null", + privSpec.getEncoded()); + + /* Test X509EncodedKeySpec extraction */ + X509EncodedKeySpec pubSpec = kf.getKeySpec(kp.getPublic(), + X509EncodedKeySpec.class); + assertNotNull("X509EncodedKeySpec should not be null", pubSpec); + assertNotNull("Encoded bytes should not be null", + pubSpec.getEncoded()); + + /* Test ECPrivateKeySpec extraction */ + ECPrivateKeySpec ecPrivSpec = kf.getKeySpec(kp.getPrivate(), + ECPrivateKeySpec.class); + assertNotNull("ECPrivateKeySpec should not be null", ecPrivSpec); + + /* Test ECPublicKeySpec extraction */ + ECPublicKeySpec ecPubSpec = kf.getKeySpec(kp.getPublic(), + ECPublicKeySpec.class); + assertNotNull("ECPublicKeySpec should not be null", ecPubSpec); + } + + @Test + public void testKeyTranslation() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Generate a test key pair using system provider */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair systemKP = kpg.generateKeyPair(); + + /* Translate keys using wolfJCE KeyFactory */ + KeyFactory wolfKF = KeyFactory.getInstance("EC", "wolfJCE"); + + PrivateKey translatedPriv = + (PrivateKey)wolfKF.translateKey(systemKP.getPrivate()); + assertNotNull("Translated private key should not be null", + translatedPriv); + assertTrue("Should be ECPrivateKey", + translatedPriv instanceof ECPrivateKey); + + PublicKey translatedPub = + (PublicKey)wolfKF.translateKey(systemKP.getPublic()); + assertNotNull("Translated public key should not be null", + translatedPub); + assertTrue("Should be ECPublicKey", + translatedPub instanceof ECPublicKey); + + /* Verify translated keys work by comparing encoded forms */ + assertArrayEquals("Private key encoded forms should match", + systemKP.getPrivate().getEncoded(), translatedPriv.getEncoded()); + assertArrayEquals("Public key encoded forms should match", + systemKP.getPublic().getEncoded(), translatedPub.getEncoded()); + } + + @Test + public void testRoundTripConversion() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Generate a test key pair using wolfJCE */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp384r1")); + KeyPair originalKP = kpg.generateKeyPair(); + + KeyFactory kf = KeyFactory.getInstance("EC", "wolfJCE"); + + /* Round trip: Key -> KeySpec -> Key */ + PKCS8EncodedKeySpec privSpec = kf.getKeySpec(originalKP.getPrivate(), + PKCS8EncodedKeySpec.class); + PrivateKey roundTripPriv = kf.generatePrivate(privSpec); + + X509EncodedKeySpec pubSpec = kf.getKeySpec(originalKP.getPublic(), + X509EncodedKeySpec.class); + PublicKey roundTripPub = kf.generatePublic(pubSpec); + + /* Verify the round trip worked */ + assertArrayEquals("Private key round trip failed", + originalKP.getPrivate().getEncoded(), + roundTripPriv.getEncoded()); + assertArrayEquals("Public key round trip failed", + originalKP.getPublic().getEncoded(), + roundTripPub.getEncoded()); + } + + @Test + public void testMultipleCurves() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + String[] curves = {"secp256r1", "secp384r1", "secp521r1"}; + KeyFactory kf = KeyFactory.getInstance("EC", "wolfJCE"); + + for (String curve : curves) { + try { + /* Generate key pair for this curve */ + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec(curve)); + KeyPair kp = kpg.generateKeyPair(); + + /* Test conversion works for this curve */ + byte[] privEncoded = kp.getPrivate().getEncoded(); + byte[] pubEncoded = kp.getPublic().getEncoded(); + + PKCS8EncodedKeySpec privSpec = + new PKCS8EncodedKeySpec(privEncoded); + PrivateKey convertedPriv = kf.generatePrivate(privSpec); + + X509EncodedKeySpec pubSpec = + new X509EncodedKeySpec(pubEncoded); + PublicKey convertedPub = kf.generatePublic(pubSpec); + + assertNotNull("Converted private key should not be null for " + + curve, convertedPriv); + assertNotNull("Converted public key should not be null for " + + curve, convertedPub); + + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to test curve " + curve + ": " + e.getMessage()); + } + } + } + + @Test + public void testInvalidKeySpecs() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + KeyFactory kf = KeyFactory.getInstance("EC", "wolfJCE"); + + /* Test null KeySpecs */ + try { + kf.generatePrivate(null); + fail("Should throw InvalidKeySpecException for null KeySpec"); + + } catch (InvalidKeySpecException e) { + /* Expected */ + } + + try { + kf.generatePublic(null); + fail("Should throw InvalidKeySpecException for null KeySpec"); + + } catch (InvalidKeySpecException e) { + /* Expected */ + } + + /* Test invalid encoded data */ + try { + PKCS8EncodedKeySpec invalidSpec = + new PKCS8EncodedKeySpec(new byte[]{1, 2, 3}); + kf.generatePrivate(invalidSpec); + fail("Should throw InvalidKeySpecException for invalid PKCS8 data"); + + } catch (InvalidKeySpecException e) { + /* Expected */ + } + + try { + X509EncodedKeySpec invalidSpec = + new X509EncodedKeySpec(new byte[]{1, 2, 3}); + kf.generatePublic(invalidSpec); + fail("Should throw InvalidKeySpecException for invalid X509 data"); + + } catch (InvalidKeySpecException e) { + /* Expected */ + } + } + + @Test + public void testECPrivateKeySpecConversionWithoutSunEC() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Remove SunEC provider temporarily if present */ + Provider sunEC = Security.getProvider("SunEC"); + if (sunEC != null) { + Security.removeProvider("SunEC"); + } + + try { + /* Generate key using wolfJCE only */ + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = kpg.generateKeyPair(); + + ECPrivateKey privKey = (ECPrivateKey) kp.getPrivate(); + + /* Extract ECPrivateKeySpec and convert back */ + KeyFactory wolfKF = KeyFactory.getInstance("EC", "wolfJCE"); + ECPrivateKeySpec keySpec = + wolfKF.getKeySpec(privKey, ECPrivateKeySpec.class); + PrivateKey convertedKey = wolfKF.generatePrivate(keySpec); + + /* Verify conversion worked */ + assertNotNull("Converted key should not be null", convertedKey); + assertTrue("Should be ECPrivateKey", + convertedKey instanceof ECPrivateKey); + + ECPrivateKey convertedECKey = (ECPrivateKey) convertedKey; + assertEquals("Private key values should match", + privKey.getS(), convertedECKey.getS()); + + } finally { + /* Restore SunEC provider if it was present */ + if (sunEC != null) { + Security.addProvider(sunEC); + } + } + } + + @Test + public void testBigIntegerEdgeCases() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Generate a reference key to get the ECParameterSpec */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair refKP = kpg.generateKeyPair(); + ECPrivateKey refPrivKey = (ECPrivateKey) refKP.getPrivate(); + ECParameterSpec params = refPrivKey.getParams(); + + KeyFactory wolfKF = KeyFactory.getInstance("EC", "wolfJCE"); + + /* Test case 1: Private key with leading zeros (small value) */ + BigInteger smallPrivateValue = BigInteger.valueOf(1); + ECPrivateKeySpec smallKeySpec = + new ECPrivateKeySpec(smallPrivateValue, params); + PrivateKey smallKey = wolfKF.generatePrivate(smallKeySpec); + assertNotNull("Small private key should be created", smallKey); + assertTrue("Should be ECPrivateKey", smallKey instanceof ECPrivateKey); + + /* Test case 2: Private key with MSB set (requires sign bit handling) */ + BigInteger largeMSBValue = + new BigInteger("FF000000000000000000000000000000" + + "00000000000000000000000000000001", 16); + ECPrivateKeySpec largeMSBKeySpec = + new ECPrivateKeySpec(largeMSBValue, params); + + try { + PrivateKey largeMSBKey = wolfKF.generatePrivate(largeMSBKeySpec); + assertNotNull("Large MSB private key should be created", + largeMSBKey); + assertTrue("Should be ECPrivateKey", + largeMSBKey instanceof ECPrivateKey); + + } catch (InvalidKeySpecException e) { + /* This might fail if the value exceeds the curve order, + * which is expected */ + assertTrue("Error should mention key size", + e.getMessage().contains("too large") || + e.getMessage().contains("positive")); + } + + /* Test case 3: Zero private key (should fail) */ + BigInteger zeroPrivateValue = BigInteger.ZERO; + ECPrivateKeySpec zeroKeySpec = + new ECPrivateKeySpec(zeroPrivateValue, params); + + try { + wolfKF.generatePrivate(zeroKeySpec); + fail("Should throw InvalidKeySpecException for zero private key"); + + } catch (InvalidKeySpecException e) { + assertTrue("Error should mention positive value", + e.getMessage().contains("positive")); + } + + /* Test case 4: Negative private key (should fail) */ + BigInteger negativePrivateValue = BigInteger.valueOf(-1); + ECPrivateKeySpec negativeKeySpec = + new ECPrivateKeySpec(negativePrivateValue, params); + + try { + wolfKF.generatePrivate(negativeKeySpec); + fail("Should throw InvalidKeySpecException for negative " + + "private key"); + + } catch (InvalidKeySpecException e) { + assertTrue("Error should mention positive value", + e.getMessage().contains("positive")); + } + } + + @Test + public void testCurveParameterValidation() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + KeyFactory wolfKF = KeyFactory.getInstance("EC", "wolfJCE"); + + for (int i = 0; i < enabledCurves.size(); i++) { + String curveName = enabledCurves.get(i); + try { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec(curveName)); + KeyPair kp = kpg.generateKeyPair(); + + ECPrivateKey privKey = (ECPrivateKey) kp.getPrivate(); + ECPrivateKeySpec keySpec = new ECPrivateKeySpec( + privKey.getS(), privKey.getParams()); + + PrivateKey convertedKey = wolfKF.generatePrivate(keySpec); + assertNotNull("Key should be created for curve " + + curveName, convertedKey); + + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to test curve " + curveName + ": " + + e.getMessage()); + } + } + + /* Test null ECParameterSpec - ECPrivateKeySpec constructor + * itself throws NPE for null params, so test for that */ + try { + ECPrivateKeySpec nullParamsSpec = + new ECPrivateKeySpec(BigInteger.ONE, null); + fail("Should throw NullPointerException for null ECParameterSpec"); + + } catch (NullPointerException e) { + /* Expected - ECPrivateKeySpec constructor rejects null params */ + } + + /* Test null private value */ + if (enabledCurves.contains("secp256r1")) { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = kpg.generateKeyPair(); + ECPrivateKey privKey = (ECPrivateKey) kp.getPrivate(); + + try { + ECPrivateKeySpec nullPrivateSpec = + new ECPrivateKeySpec(null, privKey.getParams()); + fail("Should throw NullPointerException for null " + + "private value"); + + } catch (NullPointerException e) { + /* Expected - ECPrivateKeySpec constructor rejects + * null private value */ + } + } + } + + @Test + public void testPrivateKeyBoundaryValues() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Generate a reference key to get the ECParameterSpec */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair refKP = kpg.generateKeyPair(); + ECPrivateKey refPrivKey = (ECPrivateKey) refKP.getPrivate(); + ECParameterSpec params = refPrivKey.getParams(); + + KeyFactory wolfKF = KeyFactory.getInstance("EC", "wolfJCE"); + + /* Test minimum valid private key (1) */ + BigInteger minValue = BigInteger.ONE; + ECPrivateKeySpec minKeySpec = new ECPrivateKeySpec(minValue, params); + PrivateKey minKey = wolfKF.generatePrivate(minKeySpec); + assertNotNull("Minimum private key should be created", minKey); + + ECPrivateKey minECKey = (ECPrivateKey) minKey; + assertEquals("Private key value should match", minValue, + minECKey.getS()); + + /* Test very large value that should exceed curve order */ + BigInteger veryLargeValue = + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16); + ECPrivateKeySpec largeKeySpec = + new ECPrivateKeySpec(veryLargeValue, params); + try { + wolfKF.generatePrivate(largeKeySpec); + /* If this succeeds, the value might be within the curve + * order for this test curve */ + + } catch (InvalidKeySpecException e) { + /* Check for our error messages or valid crypto-related + * error messages */ + assertTrue("Error should mention size, large value, or be " + + "crypto-related: " + e.getMessage(), + e.getMessage().contains("too large") || + e.getMessage().contains("large") || + e.getMessage().contains("size") || + e.getMessage().contains("Invalid private key") || + e.getMessage().contains("key value") || + e.getMessage().contains("not valid") || + e.getMessage().contains("error")); + } + } + + @Test + public void testMemoryCleanup() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + KeyFactory wolfKF = KeyFactory.getInstance("EC", "wolfJCE"); + + /* Generate many keys to test for memory leaks */ + for (int i = 0; i < 100; i++) { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = kpg.generateKeyPair(); + + ECPrivateKey privKey = (ECPrivateKey) kp.getPrivate(); + ECPrivateKeySpec keySpec = + new ECPrivateKeySpec(privKey.getS(), privKey.getParams()); + + PrivateKey convertedKey = wolfKF.generatePrivate(keySpec); + assertNotNull("Key " + i + " should be created", convertedKey); + } + + /* Force garbage collection to help detect leaks */ + System.gc(); + Thread.sleep(100); /* Allow GC to run */ + + /* If we get here without running out of memory or native handles, + * the test passes */ + assertTrue("Memory cleanup test completed successfully", true); + } + + @Test + public void testBackwardCompatibility() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + /* Generate key using standard approach */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp384r1")); + KeyPair originalKP = kpg.generateKeyPair(); + ECPrivateKey originalPrivKey = (ECPrivateKey) originalKP.getPrivate(); + + KeyFactory wolfKF = KeyFactory.getInstance("EC", "wolfJCE"); + + /* Test that ECPrivateKeySpec conversion produces consistent results */ + ECPrivateKeySpec keySpec = new ECPrivateKeySpec(originalPrivKey.getS(), + originalPrivKey.getParams()); + PrivateKey convertedKey1 = wolfKF.generatePrivate(keySpec); + PrivateKey convertedKey2 = wolfKF.generatePrivate(keySpec); + + /* Both conversions should produce identical encoded results */ + assertArrayEquals("Multiple conversions should produce " + + "identical results", convertedKey1.getEncoded(), + convertedKey2.getEncoded()); + + /* The converted key should have the same private value */ + ECPrivateKey convertedECKey = (ECPrivateKey) convertedKey1; + assertEquals("Private values should match", + originalPrivKey.getS(), convertedECKey.getS()); + } + + @Test + public void testErrorHandling() throws Exception { + + if (!FeatureDetect.EccEnabled()) { + return; + } + + KeyFactory wolfKF = KeyFactory.getInstance("EC", "wolfJCE"); + + /* Test null KeySpec */ + try { + wolfKF.generatePrivate(null); + fail("Should throw InvalidKeySpecException for null KeySpec"); + + } catch (InvalidKeySpecException e) { + assertTrue("Error should mention KeySpec", + e.getMessage().contains("KeySpec")); + } + + /* Test invalid ECPrivateKeySpec values - the constructor validates + * null values itself */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "wolfJCE"); + kpg.initialize(new ECGenParameterSpec("secp256r1")); + KeyPair kp = kpg.generateKeyPair(); + ECPrivateKey privKey = (ECPrivateKey) kp.getPrivate(); + + /* Test zero private value (should be rejected) */ + try { + ECPrivateKeySpec zeroSpec = new ECPrivateKeySpec(BigInteger.ZERO, + privKey.getParams()); + wolfKF.generatePrivate(zeroSpec); + fail("Should throw InvalidKeySpecException for zero private value"); + + } catch (InvalidKeySpecException e) { + assertTrue("Error should mention private key value", + e.getMessage().contains("Private key value") || + e.getMessage().contains("positive")); + } + + /* Test negative private value (should be rejected) */ + try { + ECPrivateKeySpec negativeSpec = + new ECPrivateKeySpec(BigInteger.valueOf(-1), + privKey.getParams()); + wolfKF.generatePrivate(negativeSpec); + fail("Should throw InvalidKeySpecException for negative " + + "private value"); + + } catch (InvalidKeySpecException e) { + assertTrue("Error should mention private key value", + e.getMessage().contains("Private key value") || + e.getMessage().contains("positive")); + } + } +} + diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestMd5Test.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestMd5Test.java index fc17bc58..336f35b2 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestMd5Test.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestMd5Test.java @@ -302,7 +302,8 @@ public void testMd5Threaded() throws NoSuchProviderException, NoSuchAlgorithmException, InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha256Test.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha256Test.java index 8ab0d34d..be5dcf5e 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha256Test.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha256Test.java @@ -291,7 +291,8 @@ public void testSha256Threaded() throws NoSuchProviderException, NoSuchAlgorithmException, InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha384Test.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha384Test.java index bde5bb98..0eb4b4b0 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha384Test.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha384Test.java @@ -314,7 +314,8 @@ public void testSha384Threaded() throws NoSuchProviderException, NoSuchAlgorithmException, InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha3Test.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha3Test.java index 93013a32..f23a8bc5 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha3Test.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha3Test.java @@ -325,7 +325,8 @@ public void testSha3Threaded() throws NoSuchProviderException, NoSuchAlgorithmException, InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha512Test.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha512Test.java index 06715960..bee4ca4e 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha512Test.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestSha512Test.java @@ -334,7 +334,8 @@ public void testSha512Threaded() throws NoSuchProviderException, NoSuchAlgorithmException, InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestShaTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestShaTest.java index c38423a6..60d3a99f 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestShaTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMessageDigestShaTest.java @@ -310,7 +310,8 @@ public void testShaThreaded() throws NoSuchProviderException, NoSuchAlgorithmException, InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java b/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java index e2f31af2..5a39f1a4 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java @@ -42,6 +42,7 @@ WolfCryptKeyAgreementTest.class, WolfCryptKeyGeneratorTest.class, WolfCryptKeyPairGeneratorTest.class, + WolfCryptECKeyFactoryTest.class, WolfCryptPKIXCertPathValidatorTest.class, WolfSSLKeyStoreTest.class, WolfCryptUtilTest.class diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/AesCcmTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/AesCcmTest.java index c63a443f..fe77879a 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/AesCcmTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/AesCcmTest.java @@ -717,7 +717,8 @@ public void testReuseObjectAes256() throws WolfCryptException { @Test public void testThreadedAes128() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue exceptions = @@ -781,7 +782,8 @@ public void run() { @Test public void testThreadedAes192() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue exceptions = @@ -843,7 +845,8 @@ public void run() { @Test public void testThreadedAes256() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue exceptions = @@ -1182,7 +1185,8 @@ public void testStateChecking() { @Test public void testThreading() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue exceptions = diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/EccTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/EccTest.java index 5033ee06..2a95697e 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/EccTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/EccTest.java @@ -535,5 +535,255 @@ public void threadedEccSignVerifyTest() throws InterruptedException { } } } + + @Test + public void getAllSupportedCurves() { + + /* Test that getAllSupportedCurves returns a non-null array */ + String[] curves = Ecc.getAllSupportedCurves(); + assertNotNull("getAllSupportedCurves should not return null", curves); + + /* Test that array is not empty */ + assertTrue("getAllSupportedCurves should return at least one curve", + curves.length > 0); + + /* Test that all curve names are non-null and non-empty */ + for (String curveName : curves) { + assertNotNull("Curve name should not be null", curveName); + assertTrue("Curve name should not be empty", + curveName.length() > 0); + } + + /* Test that we get expected common curves */ + boolean foundSecp256r1 = false; + for (String curveName : curves) { + if ("SECP256R1".equals(curveName)) { + foundSecp256r1 = true; + break; + } + } + assertTrue("Should find SECP256R1 in supported curves", + foundSecp256r1); + } + + @Test + public void getCurveParametersValidCurves() { + + /* Get all supported curves first */ + String[] curves = Ecc.getAllSupportedCurves(); + assertNotNull("getAllSupportedCurves should not return null", curves); + assertTrue("Should have at least one curve", curves.length > 0); + + /* Test getCurveParameters for each supported curve */ + for (String curveName : curves) { + String[] params = Ecc.getCurveParameters(curveName); + + assertNotNull("getCurveParameters should not return null for " + + curveName, params); + assertEquals("getCurveParameters should return 7 parameters for " + + curveName, 7, params.length); + + /* Verify all parameters are non-null hex strings */ + for (int i = 0; i < 6; i++) { /* first 6 are hex strings */ + assertNotNull("Parameter " + i + " should not be null for " + + curveName, params[i]); + assertTrue("Parameter " + i + " should not be empty for " + + curveName, params[i].length() > 0); + + /* Verify it's valid hex using regex */ + assertTrue("Parameter " + i + + " is not a valid hex string for " + + curveName, params[i].matches("^[0-9a-fA-F]+$")); + } + + /* Verify cofactor (last parameter) is a valid integer */ + assertNotNull("Cofactor should not be null for " + curveName, + params[6]); + assertTrue("Cofactor should not be empty for " + curveName, + params[6].length() > 0); + + Integer.parseInt(params[6]); /* should not throw */ + } + } + + @Test + public void getCurveParametersSecp256r1() { + + /* Test specific known curve parameters for SECP256R1 */ + String[] params = Ecc.getCurveParameters("SECP256R1"); + + assertNotNull("getCurveParameters should not return null", params); + assertEquals("Should return 7 parameters", 7, params.length); + + /* Verify we can parse all parameters as BigIntegers: + * params[0] - a (prime) + * params[1] - b + * params[2] - p (order) + * params[3] - n (order) + * params[4] - Gx (generator x) + * params[5] - Gy (generator y) + * params[6] - cofactor (int) */ + java.math.BigInteger p = new java.math.BigInteger(params[0], 16); + java.math.BigInteger a = new java.math.BigInteger(params[1], 16); + java.math.BigInteger b = new java.math.BigInteger(params[2], 16); + java.math.BigInteger n = new java.math.BigInteger(params[3], 16); + java.math.BigInteger gx = new java.math.BigInteger(params[4], 16); + java.math.BigInteger gy = new java.math.BigInteger(params[5], 16); + int cofactor = Integer.parseInt(params[6]); + + /* Verify reasonable field size for secp256r1 (should be 256 bits) */ + assertEquals("SECP256R1 prime should be 256 bits", 256, p.bitLength()); + + /* Verify cofactor is 1 for secp256r1 */ + assertEquals("SECP256R1 cofactor should be 1", 1, cofactor); + + /* Verify generator point coordinates are reasonable size */ + assertTrue("Generator X should be positive", + gx.compareTo(java.math.BigInteger.ZERO) > 0); + assertTrue("Generator Y should be positive", + gy.compareTo(java.math.BigInteger.ZERO) > 0); + assertTrue("Generator X should be less than prime", + gx.compareTo(p) < 0); + assertTrue("Generator Y should be less than prime", + gy.compareTo(p) < 0); + } + + @Test + public void getCurveParametersInvalidCurve() { + + /* Test with invalid curve name */ + try { + Ecc.getCurveParameters("INVALID_CURVE_NAME"); + fail("getCurveParameters should throw exception for invalid curve"); + } catch (WolfCryptException e) { + /* Expected exception */ + } + + /* Test with null curve name */ + try { + Ecc.getCurveParameters(null); + fail("getCurveParameters should throw exception for null curve"); + } catch (IllegalArgumentException e) { + /* Expected exception for null input */ + } + + /* Test with clearly invalid curve names */ + try { + Ecc.getCurveParameters("NOT_A_REAL_CURVE_NAME_12345"); + fail("getCurveParameters should throw exception for " + + "clearly invalid curve"); + } catch (WolfCryptException e) { + /* Expected exception */ + } + + try { + Ecc.getCurveParameters("XYZ_FAKE_CURVE"); + fail("getCurveParameters should throw exception for fake curve"); + } catch (WolfCryptException e) { + /* Expected exception */ + } + } + + @Test + public void getCurveParametersCaseInsensitive() { + + /* Test that curve name lookup is case insensitive */ + String[] paramsUpper = Ecc.getCurveParameters("SECP256R1"); + String[] paramsLower = Ecc.getCurveParameters("secp256r1"); + String[] paramsMixed = Ecc.getCurveParameters("SeCp256R1"); + + assertNotNull("Upper case should work", paramsUpper); + assertNotNull("Lower case should work", paramsLower); + assertNotNull("Mixed case should work", paramsMixed); + + /* All should return the same parameters */ + assertArrayEquals("Upper and lower case should return same parameters", + paramsUpper, paramsLower); + assertArrayEquals("Upper and mixed case should return same parameters", + paramsUpper, paramsMixed); + } + + @Test + public void threadedGetAllSupportedCurves() throws InterruptedException { + + /* Test that getAllSupportedCurves is thread-safe */ + int numThreads = 10; + ExecutorService service = Executors.newFixedThreadPool(numThreads); + final CountDownLatch latch = new CountDownLatch(numThreads); + final LinkedBlockingQueue results = + new LinkedBlockingQueue<>(); + + for (int i = 0; i < numThreads; i++) { + service.submit(new Runnable() { + @Override public void run() { + try { + String[] curves = Ecc.getAllSupportedCurves(); + results.add(curves); + } catch (Exception e) { + e.printStackTrace(); + results.add(null); + } finally { + latch.countDown(); + } + } + }); + } + + /* Wait for all threads to complete */ + latch.await(); + + /* Verify all results are consistent */ + String[] firstResult = results.poll(); + assertNotNull("First result should not be null", firstResult); + + while (!results.isEmpty()) { + String[] result = results.poll(); + assertNotNull("Result should not be null", result); + assertArrayEquals("All threads should return same curve list", + firstResult, result); + } + } + + @Test + public void threadedGetCurveParameters() throws InterruptedException { + + /* Test that getCurveParameters is thread-safe */ + int numThreads = 10; + ExecutorService service = Executors.newFixedThreadPool(numThreads); + final CountDownLatch latch = new CountDownLatch(numThreads); + final LinkedBlockingQueue results = + new LinkedBlockingQueue<>(); + final String testCurveName = "SECP256R1"; + + for (int i = 0; i < numThreads; i++) { + service.submit(new Runnable() { + @Override public void run() { + try { + String[] params = Ecc.getCurveParameters(testCurveName); + results.add(params); + } catch (Exception e) { + e.printStackTrace(); + results.add(null); + } finally { + latch.countDown(); + } + } + }); + } + + /* Wait for all threads to complete */ + latch.await(); + + /* Verify all results are consistent */ + String[] firstResult = results.poll(); + assertNotNull("First result should not be null", firstResult); + + while (!results.isEmpty()) { + String[] result = results.poll(); + assertNotNull("Result should not be null", result); + assertArrayEquals("All threads should return same parameters", + firstResult, result); + } + } } diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/Md5Test.java b/src/test/java/com/wolfssl/wolfcrypt/test/Md5Test.java index 27b08b8b..3df259ed 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/Md5Test.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/Md5Test.java @@ -226,7 +226,8 @@ public void copyObject() { @Test public void threadedHashTest() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/Sha224Test.java b/src/test/java/com/wolfssl/wolfcrypt/test/Sha224Test.java index df498113..3bea4c11 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/Sha224Test.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/Sha224Test.java @@ -195,7 +195,8 @@ public void copyObject() { @Test public void threadedHashTest() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/Sha256Test.java b/src/test/java/com/wolfssl/wolfcrypt/test/Sha256Test.java index aab57373..29fecc6d 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/Sha256Test.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/Sha256Test.java @@ -210,7 +210,8 @@ public void copyObject() { @Test public void threadedHashTest() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/Sha384Test.java b/src/test/java/com/wolfssl/wolfcrypt/test/Sha384Test.java index af65875e..8585145d 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/Sha384Test.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/Sha384Test.java @@ -222,7 +222,8 @@ public void copyObject() { @Test public void threadedHashTest() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/Sha3Test.java b/src/test/java/com/wolfssl/wolfcrypt/test/Sha3Test.java index 1d1c05d9..b21fa9e7 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/Sha3Test.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/Sha3Test.java @@ -251,7 +251,8 @@ public void copyObject() { @Test public void threadedHashTest() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/Sha512Test.java b/src/test/java/com/wolfssl/wolfcrypt/test/Sha512Test.java index d7fbf816..124fb4f1 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/Sha512Test.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/Sha512Test.java @@ -226,7 +226,8 @@ public void copyObject() { @Test public void threadedHashTest() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/ShaTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/ShaTest.java index 7edb7684..ca30bebe 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/ShaTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/ShaTest.java @@ -208,7 +208,8 @@ public void copyObject() { @Test public void threadedHashTest() throws InterruptedException { - int numThreads = 100; + /* Use fewer threads in CI environments to avoid resource limits */ + int numThreads = System.getenv("CI") != null ? 20 : 100; ExecutorService service = Executors.newFixedThreadPool(numThreads); final CountDownLatch latch = new CountDownLatch(numThreads); final LinkedBlockingQueue results = new LinkedBlockingQueue<>();