diff --git a/.github/workflows/windows-vs.yml b/.github/workflows/windows-vs.yml index b58bb0d0..e613788c 100644 --- a/.github/workflows/windows-vs.yml +++ b/.github/workflows/windows-vs.yml @@ -274,7 +274,7 @@ jobs: $content = Get-Content $userSettingsPath -Raw Write-Output "Original file size: $($content.Length) characters" - $newDefines = "#define WOLFSSL_KEY_GEN`n#define HAVE_CRL`n#define OPENSSL_ALL`n#define WOLFSSL_SHA224`n`n" + $newDefines = "#define WOLFSSL_KEY_GEN`n#define HAVE_CRL`n#define OPENSSL_ALL`n#define WOLFSSL_SHA224`n#define HAVE_FFDHE_2048`n#define HAVE_FFDHE_3072`n#define HAVE_FFDHE_4096`n#define HAVE_FFDHE_Q`n#define WOLFSSL_VALIDATE_FFC_IMPORT`n`n" # Try multiple possible insertion points $insertPoints = @( diff --git a/CLAUDE.md b/CLAUDE.md index 96d31f31..869d30ac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,9 @@ - Keep lines under 80 characters maximum length - MUST only use multi-line comments, no "//" style ones - MUST remove all trailing white space -- Use 4 spaces for one tab, no hard tabs +- MUST use 4 spaces for one tab, no hard tabs +- MUST use XMALLOC/XFREE for memory allocation instead of malloc/free +- MUST cast XMALLOC back to type being allocated # Source Code Organization - The source code is organized into the following directories: diff --git a/IDE/Android/app/src/main/cpp/CMakeLists.txt b/IDE/Android/app/src/main/cpp/CMakeLists.txt index cf02793f..fb2bb793 100644 --- a/IDE/Android/app/src/main/cpp/CMakeLists.txt +++ b/IDE/Android/app/src/main/cpp/CMakeLists.txt @@ -65,6 +65,7 @@ if ("${WOLFSSL_PKG_TYPE}" MATCHES "normal") -DWOLFSSL_AKID_NAME -DHAVE_CTS -DNO_DES3 -DGCM_TABLE_4BIT -DTFM_TIMING_RESISTANT -DECC_TIMING_RESISTANT -DHAVE_AESGCM -DSIZEOF_LONG=4 -DSIZEOF_LONG_LONG=8 + -DWOLFSSL_KEY_GEN -DWOLFSSL_CUSTOM_CONFIG # For gethostbyname() diff --git a/README_JCE.md b/README_JCE.md index f9b63529..f85b9825 100644 --- a/README_JCE.md +++ b/README_JCE.md @@ -185,6 +185,7 @@ The JCE provider currently supports the following algorithms: KeyFactory EC + DH (aliases: DiffieHellman, 1.2.840.113549.1.3.1) CertPathValidator Class PKIX @@ -205,9 +206,13 @@ The JCE provider currently supports the following algorithms: AlgorithmParameters AES + DH GCM RSASSA-PSS + AlgorithmParameterGenerator + DH + ### SecureRandom.getInstanceStrong() When registered as the highest priority security provider, wolfJCE will provide diff --git a/jni/include/com_wolfssl_wolfcrypt_Dh.h b/jni/include/com_wolfssl_wolfcrypt_Dh.h index 1cd22969..d006c38c 100644 --- a/jni/include/com_wolfssl_wolfcrypt_Dh.h +++ b/jni/include/com_wolfssl_wolfcrypt_Dh.h @@ -9,6 +9,16 @@ extern "C" { #endif #undef com_wolfssl_wolfcrypt_Dh_NULL #define com_wolfssl_wolfcrypt_Dh_NULL 0LL +#undef com_wolfssl_wolfcrypt_Dh_WC_FFDHE_2048 +#define com_wolfssl_wolfcrypt_Dh_WC_FFDHE_2048 256L +#undef com_wolfssl_wolfcrypt_Dh_WC_FFDHE_3072 +#define com_wolfssl_wolfcrypt_Dh_WC_FFDHE_3072 257L +#undef com_wolfssl_wolfcrypt_Dh_WC_FFDHE_4096 +#define com_wolfssl_wolfcrypt_Dh_WC_FFDHE_4096 258L +#undef com_wolfssl_wolfcrypt_Dh_WC_FFDHE_6144 +#define com_wolfssl_wolfcrypt_Dh_WC_FFDHE_6144 259L +#undef com_wolfssl_wolfcrypt_Dh_WC_FFDHE_8192 +#define com_wolfssl_wolfcrypt_Dh_WC_FFDHE_8192 260L /* * Class: com_wolfssl_wolfcrypt_Dh * Method: mallocNativeStruct_internal @@ -57,6 +67,86 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhGenerateKeyPair JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhAgree (JNIEnv *, jobject, jbyteArray, jbyteArray); +/* + * Class: com_wolfssl_wolfcrypt_Dh + * Method: wc_DhCheckPubKey + * Signature: ([B)V + */ +JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhCheckPubKey + (JNIEnv *, jobject, jbyteArray); + +/* + * Class: com_wolfssl_wolfcrypt_Dh + * Method: wc_DhCopyNamedKey + * Signature: (I)[[B + */ +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhCopyNamedKey + (JNIEnv *, jclass, jint); + +/* + * Class: com_wolfssl_wolfcrypt_Dh + * Method: wc_DhGenerateParams + * Signature: (Lcom/wolfssl/wolfcrypt/Rng;I)[[B + */ +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhGenerateParams + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: com_wolfssl_wolfcrypt_Dh + * Method: wc_DhImportKeyPair + * Signature: ([B[B[B[B)V + */ +JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhImportKeyPair + (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray, jbyteArray); + +/* + * Class: com_wolfssl_wolfcrypt_Dh + * Method: wc_DhExportKeyPair + * Signature: ()[[B + */ +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhExportKeyPair + (JNIEnv *, jobject); + +/* + * Class: com_wolfssl_wolfcrypt_Dh + * Method: wc_DhExportParams + * Signature: ()[[B + */ +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhExportParams + (JNIEnv *, jobject); + +/* + * Class: com_wolfssl_wolfcrypt_Dh + * Method: wc_DhPrivateKeyDecode + * Signature: ([B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPrivateKeyDecode + (JNIEnv *, jobject, jbyteArray); + +/* + * Class: com_wolfssl_wolfcrypt_Dh + * Method: wc_DhPrivateKeyEncode + * Signature: ()[B + */ +JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPrivateKeyEncode + (JNIEnv *, jobject); + +/* + * Class: com_wolfssl_wolfcrypt_Dh + * Method: wc_DhPublicKeyDecode + * Signature: ([B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPublicKeyDecode + (JNIEnv *, jobject, jbyteArray); + +/* + * Class: com_wolfssl_wolfcrypt_Dh + * Method: wc_DhPublicKeyEncode + * Signature: ()[B + */ +JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPublicKeyEncode + (JNIEnv *, jobject); + #ifdef __cplusplus } #endif diff --git a/jni/jni_aesccm.c b/jni/jni_aesccm.c index d0e47ce2..63cbf5e8 100644 --- a/jni/jni_aesccm.c +++ b/jni/jni_aesccm.c @@ -194,7 +194,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesCcm_wc_1AesCcmEncrypt /* in can be NULL if inLen is 0 - case with only AAD to gen tag */ if ((inLen != 0 && in == NULL) || nonce == NULL || authTag == NULL || - nonceSz < 7 || nonceSz > 13 || authTagSz > WC_AES_BLOCK_SIZE) { + nonceSz < 7 || nonceSz > 13 || authTagSz > AES_BLOCK_SIZE) { ret = BAD_FUNC_ARG; } @@ -327,7 +327,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesCcm_wc_1AesCcmDecrypt /* in can be NULL if inLen is 0 - case with only AAD to verify tag */ if ((inLen != 0 && in == NULL) || nonce == NULL || authTag == NULL || - nonceSz < 7 || nonceSz > 13 || authTagSz > WC_AES_BLOCK_SIZE) { + nonceSz < 7 || nonceSz > 13 || authTagSz > AES_BLOCK_SIZE) { ret = BAD_FUNC_ARG; } diff --git a/jni/jni_aesgcm.c b/jni/jni_aesgcm.c index f5565887..e6fed283 100644 --- a/jni/jni_aesgcm.c +++ b/jni/jni_aesgcm.c @@ -193,7 +193,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncrypt } /* in may be null, users might only pass in AAD to generate tag */ - if (authTagSz > WC_AES_BLOCK_SIZE || iv == NULL || ivSz == 0 || + if (authTagSz > AES_BLOCK_SIZE || iv == NULL || ivSz == 0 || ((authTagSz > 0) && (authTag == NULL)) || ((authInSz > 0) && (authIn == NULL))) { ret = BAD_FUNC_ARG; @@ -329,7 +329,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmDecrypt /* If inLen is non-zero, both in and out must be set. If inLen is 0, * in and out are don't cares, as this is the GMAC case */ if (iv == NULL || ivSz == 0 || (inLen != 0 && in == NULL) || - authTag == NULL || (authTagSz > WC_AES_BLOCK_SIZE) || authTagSz == 0) { + authTag == NULL || (authTagSz > AES_BLOCK_SIZE) || authTagSz == 0) { ret = BAD_FUNC_ARG; } diff --git a/jni/jni_dh.c b/jni/jni_dh.c index 404251c7..1a61ed0f 100644 --- a/jni/jni_dh.c +++ b/jni/jni_dh.c @@ -27,6 +27,7 @@ #include #endif #include +#include #include #include @@ -429,3 +430,909 @@ Java_com_wolfssl_wolfcrypt_Dh_wc_1DhAgree( return result; } +JNIEXPORT jobjectArray JNICALL +Java_com_wolfssl_wolfcrypt_Dh_wc_1DhCopyNamedKey( + JNIEnv* env, jclass class, jint name) +{ + jobjectArray result = NULL; + +#ifndef NO_DH + int ret = 0; + byte* p = NULL; + byte* g = NULL; + word32 pSz = 0; + word32 gSz = 0; + jbyteArray pArray = NULL; + jbyteArray gArray = NULL; + + /* wc_DhCopyNamedKey() not available in FIPSv2 */ + #if !defined(HAVE_FIPS) || \ + (defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION >= 5)) + + /* Get sizes */ + ret = wc_DhCopyNamedKey(name, NULL, &pSz, NULL, &gSz, NULL, NULL); + if (ret != 0 && ret != LENGTH_ONLY_E) { + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + + /* Allocate buffers */ + p = (byte*)XMALLOC(pSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (p == NULL) { + throwOutOfMemoryException(env, "Failed to allocate p buffer"); + return NULL; + } + XMEMSET(p, 0, pSz); + + g = (byte*)XMALLOC(gSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (g == NULL) { + XFREE(p, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwOutOfMemoryException(env, "Failed to allocate g buffer"); + return NULL; + } + XMEMSET(g, 0, gSz); + + /* Copy named key parameters */ + ret = wc_DhCopyNamedKey(name, p, &pSz, g, &gSz, NULL, NULL); + if (ret != 0) { + XFREE(p, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(g, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + + #else + /* FIPSv2 fallback using individual wc_Dh_ffdheXXXX_Get() functions. + * These functions return const DhParams* containing p, g, and + * optionally q parameters. */ + const DhParams* params = NULL; + + /* Get FFDHE parameters based on named group */ + switch (name) { + #ifdef HAVE_FFDHE_2048 + case 256: + params = wc_Dh_ffdhe2048_Get(); + break; + #endif + + #ifdef HAVE_FFDHE_3072 + case 257: + params = wc_Dh_ffdhe3072_Get(); + break; + #endif + + #ifdef HAVE_FFDHE_4096 + case 258: + params = wc_Dh_ffdhe4096_Get(); + break; + #endif + + #ifdef HAVE_FFDHE_6144 + case 259: + params = wc_Dh_ffdhe6144_Get(); + break; + #endif + + #ifdef HAVE_FFDHE_8192 + case 260: + params = wc_Dh_ffdhe8192_Get(); + break; + #endif + + default: + throwWolfCryptException(env, "Unsupported FFDHE group"); + return NULL; + } + + if (params == NULL) { + throwWolfCryptException(env, + "Failed to get FFDHE parameters from native wolfSSL library"); + return NULL; + } + + /* Get sizes from DhParams structure */ + pSz = params->p_len; + gSz = params->g_len; + + /* Allocate buffers and copy from const params */ + p = (byte*)XMALLOC(pSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (p == NULL) { + throwOutOfMemoryException(env, "Failed to allocate p buffer"); + return NULL; + } + XMEMCPY(p, params->p, pSz); + + g = (byte*)XMALLOC(gSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (g == NULL) { + XFREE(p, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwOutOfMemoryException(env, "Failed to allocate g buffer"); + return NULL; + } + XMEMCPY(g, params->g, gSz); + + #endif /* FIPS version check */ + + /* Create byte arrays for p and g */ + pArray = (*env)->NewByteArray(env, pSz); + gArray = (*env)->NewByteArray(env, gSz); + + if (pArray && gArray) { + (*env)->SetByteArrayRegion(env, pArray, 0, pSz, (const jbyte*)p); + (*env)->SetByteArrayRegion(env, gArray, 0, gSz, (const jbyte*)g); + + /* Create object array to hold both p and g */ + result = (*env)->NewObjectArray(env, 2, + (*env)->FindClass(env, "[B"), NULL); + + if (result) { + (*env)->SetObjectArrayElement(env, result, 0, pArray); + (*env)->SetObjectArrayElement(env, result, 1, gArray); + } + else { + throwWolfCryptException(env, + "Failed to allocate DH params array"); + } + } + else { + throwWolfCryptException(env, "Failed to allocate DH params"); + } + + (void)ret; + + LogStr("wc_DhCopyNamedKey(name=%d) = %d\n", name, ret); + LogStr("p[%u]: [%p]\n", (word32)pSz, p); + LogHex((byte*) p, 0, pSz); + LogStr("g[%u]: [%p]\n", (word32)gSz, g); + LogHex((byte*) g, 0, gSz); + + XFREE(p, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(g, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#else + throwNotCompiledInException(env); +#endif + + return result; +} + +JNIEXPORT jobjectArray JNICALL +Java_com_wolfssl_wolfcrypt_Dh_wc_1DhGenerateParams( + JNIEnv* env, jclass class, jobject rng_object, jint modSz) +{ + jobjectArray result = NULL; + +#if !defined(NO_DH) && defined(WOLFSSL_KEY_GEN) + int ret = 0; + DhKey* dh = NULL; + RNG* rng = NULL; + byte* p = NULL; + byte* g = NULL; + word32 pSz = 0; + word32 gSz = 0; + + /* Get RNG object */ + rng = (RNG*) getNativeStruct(env, rng_object); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception */ + return NULL; + } + + if (rng == NULL || modSz <= 0) { + throwWolfCryptException(env, + "Invalid arguments to wc_DhGenerateParams"); + return NULL; + } + + /* Allocate temporary DhKey structure */ + dh = (DhKey*)XMALLOC(sizeof(DhKey), NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (dh == NULL) { + throwOutOfMemoryException(env, "Failed to allocate DhKey"); + return NULL; + } + XMEMSET(dh, 0, sizeof(DhKey)); + + /* Initialize DH key */ + ret = wc_InitDhKey(dh); + if (ret != 0) { + XFREE(dh, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + + /* Generate DH parameters */ + ret = wc_DhGenerateParams(rng, modSz, dh); + if (ret != 0) { + wc_FreeDhKey(dh); + XFREE(dh, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + + /* Get sizes for p and g - use modSz in bytes as buffer size */ + pSz = (modSz + 7) / 8; /* modSz is in bits, convert to bytes */ + gSz = (modSz + 7) / 8; + + /* Allocate buffers for p and g */ + p = (byte*)XMALLOC(pSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (p == NULL) { + wc_FreeDhKey(dh); + XFREE(dh, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwOutOfMemoryException(env, "Failed to allocate p buffer"); + return NULL; + } + XMEMSET(p, 0, pSz); + + g = (byte*)XMALLOC(gSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (g == NULL) { + XFREE(p, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeDhKey(dh); + XFREE(dh, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwOutOfMemoryException(env, "Failed to allocate g buffer"); + return NULL; + } + XMEMSET(g, 0, gSz); + + /* Export parameters from DhKey */ + ret = wc_DhExportParamsRaw(dh, p, &pSz, NULL, NULL, g, &gSz); + if (ret != 0) { + XFREE(p, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(g, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeDhKey(dh); + XFREE(dh, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + + /* Create byte arrays for p and g */ + jbyteArray pArray = (*env)->NewByteArray(env, pSz); + jbyteArray gArray = (*env)->NewByteArray(env, gSz); + + if (pArray && gArray) { + (*env)->SetByteArrayRegion(env, pArray, 0, pSz, (const jbyte*)p); + (*env)->SetByteArrayRegion(env, gArray, 0, gSz, (const jbyte*)g); + + /* Create object array to hold both p and g */ + result = (*env)->NewObjectArray(env, 2, + (*env)->FindClass(env, "[B"), NULL); + + if (result) { + (*env)->SetObjectArrayElement(env, result, 0, pArray); + (*env)->SetObjectArrayElement(env, result, 1, gArray); + } + else { + throwWolfCryptException(env, + "Failed to allocate DH params array"); + } + } + else { + throwWolfCryptException(env, "Failed to allocate DH params"); + } + + LogStr("wc_DhGenerateParams(rng=%p, modSz=%d) = %d\n", rng, modSz, ret); + LogStr("p[%u]: [%p]\n", (word32)pSz, p); + LogHex((byte*) p, 0, pSz); + LogStr("g[%u]: [%p]\n", (word32)gSz, g); + LogHex((byte*) g, 0, gSz); + + /* Clean up */ + XFREE(p, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(g, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeDhKey(dh); + XFREE(dh, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#else + (void)rng_object; + (void)modSz; + throwNotCompiledInException(env); +#endif + + return result; +} + +JNIEXPORT void JNICALL +Java_com_wolfssl_wolfcrypt_Dh_wc_1DhImportKeyPair( + JNIEnv* env, jobject this, jbyteArray priv_object, jbyteArray pub_object, + jbyteArray p_object, jbyteArray g_object) +{ +#if !defined(NO_DH) && defined(WOLFSSL_DH_EXTRA) + int ret = 0; + DhKey* key = NULL; + byte* priv = NULL; + byte* pub = NULL; + byte* p = NULL; + byte* g = NULL; + word32 privSz = 0, pubSz = 0, pSz = 0, gSz = 0; + + key = (DhKey*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception */ + return; + } + + if (key == NULL) { + throwWolfCryptException(env, "Invalid DhKey object"); + return; + } + + /* Get parameters p and g (required) */ + p = getByteArray(env, p_object); + pSz = getByteArrayLength(env, p_object); + g = getByteArray(env, g_object); + gSz = getByteArrayLength(env, g_object); + + if (p == NULL || g == NULL) { + releaseByteArray(env, p_object, p, JNI_ABORT); + releaseByteArray(env, g_object, g, JNI_ABORT); + throwWolfCryptException(env, "DH parameters p and g are required"); + return; + } + + /* Set DH parameters first */ + ret = wc_DhSetKey(key, p, pSz, g, gSz); + + if (ret == 0) { + /* Get private key if provided */ + if (priv_object != NULL) { + priv = getByteArray(env, priv_object); + privSz = getByteArrayLength(env, priv_object); + } + + /* Get public key if provided */ + if (pub_object != NULL) { + pub = getByteArray(env, pub_object); + pubSz = getByteArrayLength(env, pub_object); + } + + /* Import key pair using WOLFSSL_DH_EXTRA functions */ + if (priv != NULL || pub != NULL) { + PRIVATE_KEY_UNLOCK(); + ret = wc_DhImportKeyPair(key, priv, privSz, pub, pubSz); + PRIVATE_KEY_LOCK(); + } + } + + releaseByteArray(env, p_object, p, JNI_ABORT); + releaseByteArray(env, g_object, g, JNI_ABORT); + if (priv_object != NULL) { + releaseByteArray(env, priv_object, priv, JNI_ABORT); + } + if (pub_object != NULL) { + releaseByteArray(env, pub_object, pub, JNI_ABORT); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } + + LogStr("wc_DhImportKeyPair(key=%p, priv, privSz, pub, pubSz) = %d\n", + key, ret); +#else + (void)priv_object; + (void)pub_object; + (void)p_object; + (void)g_object; + throwNotCompiledInException(env); +#endif +} + +JNIEXPORT jobjectArray JNICALL +Java_com_wolfssl_wolfcrypt_Dh_wc_1DhExportKeyPair( + JNIEnv* env, jobject this) +{ + jobjectArray result = NULL; + +#if !defined(NO_DH) && defined(WOLFSSL_DH_EXTRA) + int ret = 0; + DhKey* key = NULL; + byte* priv = NULL; + byte* pub = NULL; + word32 privSz = 0, pubSz = 0; + jbyteArray privArray = NULL; + jbyteArray pubArray = NULL; + + key = (DhKey*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception */ + return NULL; + } + + if (key == NULL) { + throwWolfCryptException(env, "Invalid DhKey object"); + return NULL; + } + + /* Get sizes for private and public keys */ + ret = wc_DhExportKeyPair(key, NULL, &privSz, NULL, &pubSz); + if (ret != LENGTH_ONLY_E && ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + + /* Allocate buffers */ + priv = (byte*)XMALLOC(privSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (priv == NULL) { + throwOutOfMemoryException(env, "Failed to allocate private key buffer"); + return NULL; + } + XMEMSET(priv, 0, privSz); + + pub = (byte*)XMALLOC(pubSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (pub == NULL) { + XMEMSET(priv, 0, privSz); + XFREE(priv, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwOutOfMemoryException(env, "Failed to allocate public key buffer"); + return NULL; + } + XMEMSET(pub, 0, pubSz); + + /* Export key pair */ + PRIVATE_KEY_UNLOCK(); + ret = wc_DhExportKeyPair(key, priv, &privSz, pub, &pubSz); + PRIVATE_KEY_LOCK(); + + if (ret == 0) { + /* Create byte arrays */ + privArray = (*env)->NewByteArray(env, privSz); + pubArray = (*env)->NewByteArray(env, pubSz); + + if (privArray != NULL && pubArray != NULL) { + (*env)->SetByteArrayRegion(env, privArray, 0, privSz, + (const jbyte*)priv); + (*env)->SetByteArrayRegion(env, pubArray, 0, pubSz, + (const jbyte*)pub); + + /* Create object array [priv, pub] */ + result = (*env)->NewObjectArray(env, 2, + (*env)->FindClass(env, "[B"), NULL); + + if (result) { + (*env)->SetObjectArrayElement(env, result, 0, privArray); + (*env)->SetObjectArrayElement(env, result, 1, pubArray); + } + else { + LogStr("Failed to allocate key pair array\n"); + ret = MEMORY_E; + } + } + else { + LogStr("Failed to allocate key pair byte arrays\n"); + ret = MEMORY_E; + } + } + + LogStr("wc_DhExportKeyPair(key=%p) = %d\n", key, ret); + + /* Clean up */ + if (priv != NULL) { + XMEMSET(priv, 0, privSz); + XFREE(priv, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + if (pub != NULL) { + XMEMSET(pub, 0, pubSz); + XFREE(pub, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } +#else + throwNotCompiledInException(env); +#endif + + return result; +} + +JNIEXPORT jobjectArray JNICALL +Java_com_wolfssl_wolfcrypt_Dh_wc_1DhExportParams( + JNIEnv* env, jobject this) +{ + jobjectArray result = NULL; + +#ifndef NO_DH + int ret = 0; + DhKey* key = NULL; + byte* p = NULL; + byte* g = NULL; + byte* q = NULL; + word32 pSz = 0, gSz = 0, qSz = 0; + jbyteArray pArray = NULL; + jbyteArray gArray = NULL; + jbyteArray qArray = NULL; + int hasQ = 0; + + key = (DhKey*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception */ + return NULL; + } + + if (key == NULL) { + throwWolfCryptException(env, "Invalid DhKey object"); + return NULL; + } + + /* Get sizes - try with q first */ + ret = wc_DhExportParamsRaw(key, NULL, &pSz, NULL, &qSz, NULL, &gSz); + if (ret != LENGTH_ONLY_E && ret != 0) { + /* Try without q */ + ret = wc_DhExportParamsRaw(key, NULL, &pSz, NULL, NULL, NULL, &gSz); + if (ret != LENGTH_ONLY_E && ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + hasQ = 0; + } + else { + hasQ = (qSz > 0); + } + + /* Allocate buffers */ + p = (byte*)XMALLOC(pSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (p == NULL) { + throwOutOfMemoryException(env, "Failed to allocate p buffer"); + return NULL; + } + XMEMSET(p, 0, pSz); + + g = (byte*)XMALLOC(gSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (g == NULL) { + XFREE(p, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwOutOfMemoryException(env, "Failed to allocate g buffer"); + return NULL; + } + XMEMSET(g, 0, gSz); + + if (hasQ) { + q = (byte*)XMALLOC(qSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (q == NULL) { + XFREE(p, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(g, NULL, DYNAMIC_TYPE_TMP_BUFFER); + throwOutOfMemoryException(env, "Failed to allocate q buffer"); + return NULL; + } + XMEMSET(q, 0, qSz); + } + + /* Export parameters */ + ret = wc_DhExportParamsRaw(key, p, &pSz, q, hasQ ? &qSz : NULL, g, &gSz); + + if (ret == 0) { + /* Create byte arrays */ + pArray = (*env)->NewByteArray(env, pSz); + gArray = (*env)->NewByteArray(env, gSz); + if (hasQ) { + qArray = (*env)->NewByteArray(env, qSz); + } + + if (pArray && gArray && (!hasQ || qArray)) { + (*env)->SetByteArrayRegion(env, pArray, 0, pSz, (const jbyte*)p); + (*env)->SetByteArrayRegion(env, gArray, 0, gSz, (const jbyte*)g); + if (hasQ) { + (*env)->SetByteArrayRegion(env, qArray, 0, qSz, + (const jbyte*)q); + } + + /* Create object array [p, g] or [p, g, q] */ + result = (*env)->NewObjectArray(env, hasQ ? 3 : 2, + (*env)->FindClass(env, "[B"), NULL); + + if (result) { + (*env)->SetObjectArrayElement(env, result, 0, pArray); + (*env)->SetObjectArrayElement(env, result, 1, gArray); + if (hasQ) { + (*env)->SetObjectArrayElement(env, result, 2, qArray); + } + } + else { + LogStr("Failed to allocate params array\n"); + ret = MEMORY_E; + } + } + else { + LogStr("Failed to allocate param byte arrays\n"); + ret = MEMORY_E; + } + } + + LogStr("wc_DhExportParams(key=%p) = %d\n", key, ret); + + /* Clean up */ + if (p != NULL) { + XFREE(p, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + if (g != NULL) { + XFREE(g, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + if (q != NULL) { + XFREE(q, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } + +#else + throwNotCompiledInException(env); +#endif + + return result; +} + +JNIEXPORT jbyteArray JNICALL +Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPrivateKeyDecode( + JNIEnv* env, jobject this, jbyteArray pkcs8_object) +{ + jbyteArray result = NULL; + +#ifndef NO_DH + int ret = 0; + DhKey* key = NULL; + byte* pkcs8 = NULL; + word32 pkcs8Sz = 0; + word32 idx = 0; + + key = (DhKey*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception */ + return NULL; + } + + if (key == NULL) { + throwWolfCryptException(env, "Invalid DhKey object"); + return NULL; + } + + pkcs8 = getByteArray(env, pkcs8_object); + pkcs8Sz = getByteArrayLength(env, pkcs8_object); + + if (pkcs8 == NULL) { + throwWolfCryptException(env, "PKCS#8 data cannot be null"); + return NULL; + } + + /* Decode PKCS#8 private key */ + PRIVATE_KEY_UNLOCK(); + ret = wc_DhKeyDecode(pkcs8, &idx, key, pkcs8Sz); + PRIVATE_KEY_LOCK(); + + if (ret == 0) { + /* Return the same DER data (validated) */ + result = (*env)->NewByteArray(env, pkcs8Sz); + if (result) { + (*env)->SetByteArrayRegion(env, result, 0, pkcs8Sz, + (const jbyte*)pkcs8); + } + else { + LogStr("Failed to allocate result array\n"); + ret = MEMORY_E; + } + } + + LogStr("wc_DhKeyDecode(pkcs8=%p, key=%p) = %d\n", pkcs8, key, ret); + + releaseByteArray(env, pkcs8_object, pkcs8, JNI_ABORT); + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } +#else + (void)pkcs8_object; + throwNotCompiledInException(env); +#endif + + return result; +} + +JNIEXPORT jbyteArray JNICALL +Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPrivateKeyEncode( + JNIEnv* env, jobject this) +{ + jbyteArray result = NULL; + +#if !defined(NO_DH) && (!defined(HAVE_FIPS) || \ + (defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION >= 5))) + int ret = 0; + DhKey* key = NULL; + byte* der = NULL; + word32 derSz = 0; + + key = (DhKey*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception */ + return NULL; + } + + if (key == NULL) { + throwWolfCryptException(env, "Invalid DhKey object"); + return NULL; + } + + /* wc_DhPrivKeyToDer() returns LENGTH_ONLY_E when output buffer is NULL + * and sets derSz to required size. */ + PRIVATE_KEY_UNLOCK(); + ret = wc_DhPrivKeyToDer(key, NULL, &derSz); + PRIVATE_KEY_LOCK(); + + if (ret != LENGTH_ONLY_E) { + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + + /* Allocate buffer with exact size needed */ + der = (byte*)XMALLOC(derSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (der == NULL) { + throwOutOfMemoryException(env, "Failed to allocate DER buffer"); + return NULL; + } + XMEMSET(der, 0, derSz); + + /* Encode PKCS#8 private key */ + PRIVATE_KEY_UNLOCK(); + ret = wc_DhPrivKeyToDer(key, der, &derSz); + PRIVATE_KEY_LOCK(); + + if (ret >= 0) { + derSz = ret; /* Actual size written */ + result = (*env)->NewByteArray(env, derSz); + if (result) { + (*env)->SetByteArrayRegion(env, result, 0, derSz, + (const jbyte*)der); + } + else { + LogStr("Failed to allocate result array\n"); + ret = MEMORY_E; + } + } + + LogStr("wc_DhPrivKeyToDer(key=%p) = %d\n", key, ret); + + /* Clean up */ + if (der != NULL) { + XMEMSET(der, 0, derSz); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } +#else + throwNotCompiledInException(env); +#endif + + return result; +} + +JNIEXPORT jbyteArray JNICALL +Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPublicKeyDecode( + JNIEnv* env, jobject this, jbyteArray x509_object) +{ + jbyteArray result = NULL; + +#ifndef NO_DH + int ret = 0; + DhKey* key = NULL; + byte* x509 = NULL; + word32 x509Sz = 0; + word32 idx = 0; + + key = (DhKey*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception */ + return NULL; + } + + if (key == NULL) { + throwWolfCryptException(env, "Invalid DhKey object"); + return NULL; + } + + x509 = getByteArray(env, x509_object); + x509Sz = getByteArrayLength(env, x509_object); + + if (x509 == NULL) { + throwWolfCryptException(env, "X.509 data cannot be null"); + return NULL; + } + + /* Decode X.509 public key */ + ret = wc_DhKeyDecode(x509, &idx, key, x509Sz); + + if (ret == 0) { + /* Return the same DER data (validated) */ + result = (*env)->NewByteArray(env, x509Sz); + if (result) { + (*env)->SetByteArrayRegion(env, result, 0, x509Sz, + (const jbyte*)x509); + } + else { + LogStr("Failed to allocate result array\n"); + ret = MEMORY_E; + } + } + + LogStr("wc_DhKeyDecode(x509=%p, key=%p) = %d\n", x509, key, ret); + + releaseByteArray(env, x509_object, x509, JNI_ABORT); + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } +#else + (void)x509_object; + throwNotCompiledInException(env); +#endif + + return result; +} + +JNIEXPORT jbyteArray JNICALL +Java_com_wolfssl_wolfcrypt_Dh_wc_1DhPublicKeyEncode( + JNIEnv* env, jobject this) +{ + jbyteArray result = NULL; + +#if !defined(NO_DH) && (!defined(HAVE_FIPS) || \ + (defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION >= 5))) + int ret = 0; + DhKey* key = NULL; + byte* der = NULL; + word32 derSz = 0; + + key = (DhKey*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + /* getNativeStruct may throw exception */ + return NULL; + } + + if (key == NULL) { + throwWolfCryptException(env, "Invalid DhKey object"); + return NULL; + } + + /* wc_DhPubKeyToDer() returns LENGTH_ONLY_E when output buffer is NULL and + * sets derSz to required size. */ + ret = wc_DhPubKeyToDer(key, NULL, &derSz); + if (ret != LENGTH_ONLY_E) { + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + + /* Allocate buffer with exact size needed */ + der = (byte*)XMALLOC(derSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (der == NULL) { + throwOutOfMemoryException(env, "Failed to allocate DER buffer"); + return NULL; + } + XMEMSET(der, 0, derSz); + + /* Encode X.509 public key */ + ret = wc_DhPubKeyToDer(key, der, &derSz); + + if (ret >= 0) { + derSz = ret; /* Actual size written */ + result = (*env)->NewByteArray(env, derSz); + if (result) { + (*env)->SetByteArrayRegion(env, result, 0, derSz, + (const jbyte*)der); + } + else { + LogStr("Failed to allocate result array\n"); + ret = MEMORY_E; + } + } + + LogStr("wc_DhPubKeyToDer(key=%p) = %d\n", key, ret); + + /* Clean up */ + if (der != NULL) { + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } +#else + throwNotCompiledInException(env); +#endif + + return result; +} + diff --git a/scripts/infer.sh b/scripts/infer.sh index fe0731bb..680cb1d8 100755 --- a/scripts/infer.sh +++ b/scripts/infer.sh @@ -71,8 +71,14 @@ infer --fail-on-issue run -- javac \ src/main/java/com/wolfssl/wolfcrypt/WolfObject.java \ src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManager.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptAesParameters.java \ + src/main/java/com/wolfssl/provider/jce/WolfCryptASN1Util.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/WolfCryptDhParameterGenerator.java \ + src/main/java/com/wolfssl/provider/jce/WolfCryptDhParameters.java \ + src/main/java/com/wolfssl/provider/jce/WolfCryptDHKeyFactory.java \ + src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java \ + src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.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 \ diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptASN1Util.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptASN1Util.java new file mode 100644 index 00000000..9d81defc --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptASN1Util.java @@ -0,0 +1,484 @@ +/* WolfCryptASN1Util.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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; + +/** + * Utility class for ASN.1/DER encoding and decoding operations. + * + * This class provides helper methods for manually constructing DER-encoded + * structures when native wolfSSL functionality is not available (ex: + * FIPS builds that do not define WOLFSSL_DH_EXTRA). + */ +public class WolfCryptASN1Util { + + /* ASN.1 Universal Tags */ + private static final byte ASN1_INTEGER = 0x02; + private static final byte ASN1_BIT_STRING = 0x03; + private static final byte ASN1_OCTET_STRING = 0x04; + private static final byte ASN1_OBJECT_IDENTIFIER = 0x06; + private static final byte ASN1_SEQUENCE = 0x30; + + /* DH Algorithm OID: 1.2.840.113549.1.3.1 (pkcs-3) */ + private static final byte[] DH_ALGORITHM_OID = { + (byte)0x06, (byte)0x09, /* OID tag and length */ + (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0x86, + (byte)0xF7, (byte)0x0D, (byte)0x01, (byte)0x03, + (byte)0x01 + }; + + /** + * Private constructor, all methods are static. + */ + private WolfCryptASN1Util() { + } + + /** + * Encode a BigInteger as a DER INTEGER. + * + * DER INTEGER format: + * - Tag: 0x02 + * - Length: variable + * - Value: big-endian bytes, with leading 0x00 if MSB is set + * + * @param value the BigInteger to encode + * + * @return DER-encoded INTEGER (tag + length + value) + * + * @throws IllegalArgumentException if value is null, or encoding fails + */ + public static byte[] encodeDERInteger(BigInteger value) + throws IllegalArgumentException { + + byte[] valueBytes; + ByteArrayOutputStream out; + + if (value == null) { + throw new IllegalArgumentException("BigInteger cannot be null"); + } + + /* Get big-endian byte representation */ + valueBytes = value.toByteArray(); + + /* BigInteger.toByteArray() handles sign bit correctly: + * - For positive numbers, adds leading 0x00 if MSB is set + * - For negative numbers (shouldn't happen), uses two's complement */ + out = new ByteArrayOutputStream(); + + try { + out.write(ASN1_INTEGER); + out.write(encodeDERLength(valueBytes.length)); + out.write(valueBytes); + + return out.toByteArray(); + + } catch (IOException e) { + throw new IllegalArgumentException( + "Failed to encode INTEGER: " + e.getMessage(), e); + } + } + + /** + * Encode contents as a DER SEQUENCE. + * + * DER SEQUENCE format: + * - Tag: 0x30 + * - Length: variable + * - Contents: concatenated DER-encoded elements + * + * @param contents the already-encoded contents to wrap in SEQUENCE + * + * @return DER-encoded SEQUENCE (tag + length + contents) + * + * @throws IllegalArgumentException if contents is null or encoding fails + */ + public static byte[] encodeDERSequence(byte[] contents) + throws IllegalArgumentException { + + ByteArrayOutputStream out; + + if (contents == null) { + throw new IllegalArgumentException("Contents cannot be null"); + } + + out = new ByteArrayOutputStream(); + + try { + out.write(ASN1_SEQUENCE); + out.write(encodeDERLength(contents.length)); + out.write(contents); + + return out.toByteArray(); + + } catch (IOException e) { + throw new IllegalArgumentException( + "Failed to encode SEQUENCE: " + e.getMessage(), e); + } + } + + /** + * Encode contents as a DER OCTET STRING. + * + * DER OCTET STRING format: + * - Tag: 0x04 + * - Length: variable + * - Contents: raw bytes + * + * @param contents the bytes to encode as OCTET STRING + * + * @return DER-encoded OCTET STRING (tag + length + contents) + * + * @throws IllegalArgumentException if contents is null or encoding fails + */ + public static byte[] encodeDEROctetString(byte[] contents) + throws IllegalArgumentException { + + ByteArrayOutputStream out; + + if (contents == null) { + throw new IllegalArgumentException("Contents cannot be null"); + } + + out = new ByteArrayOutputStream(); + + try { + out.write(ASN1_OCTET_STRING); + out.write(encodeDERLength(contents.length)); + out.write(contents); + + return out.toByteArray(); + + } catch (IOException e) { + throw new IllegalArgumentException( + "Failed to encode OCTET STRING: " + e.getMessage(), e); + } + } + + /** + * Encode contents as a DER BIT STRING. + * + * DER BIT STRING format: + * - Tag: 0x03 + * - Length: variable (includes unused bits byte) + * - Unused bits: 0x00 (we always use whole bytes) + * - Contents: raw bytes + * + * @param contents the bytes to encode as BIT STRING + * + * @return DER-encoded BIT STRING (tag + length + 0x00 + contents) + * + * @throws IllegalArgumentException if contents is null + */ + public static byte[] encodeDERBitString(byte[] contents) + throws IllegalArgumentException { + + ByteArrayOutputStream out; + + if (contents == null) { + throw new IllegalArgumentException("Contents cannot be null"); + } + + out = new ByteArrayOutputStream(); + + try { + out.write(ASN1_BIT_STRING); + /* Length includes the unused bits byte */ + out.write(encodeDERLength(contents.length + 1)); + /* Unused bits byte - always 0x00 for whole bytes */ + out.write(0x00); + out.write(contents); + + return out.toByteArray(); + + } catch (IOException e) { + throw new IllegalArgumentException( + "Failed to encode BIT STRING: " + e.getMessage(), e); + } + } + + /** + * Encode a length value using DER length encoding rules. + * + * DER length encoding: + * - Short form (length less than 128): one byte with value + * - Long form (length greater than or equal to 128): first byte has + * bit 7 set and lower 7 bits indicate number of following length + * bytes, then length bytes in big-endian order + * + * Examples: + * - Length 5: 0x05 + * - Length 200: 0x81 0xC8 (long form, 1 byte follows) + * - Length 1000: 0x82 0x03 0xE8 (long form, 2 bytes follow) + * + * @param length the length to encode (must be non-negative) + * + * @return DER-encoded length bytes + * + * @throws IllegalArgumentException if length is negative + */ + public static byte[] encodeDERLength(int length) + throws IllegalArgumentException { + + int numLengthBytes = 0; + int tempLength; + byte[] encoded; + + if (length < 0) { + throw new IllegalArgumentException( + "Length cannot be negative: " + length); + } + + /* Short form: length < 128 */ + if (length < 128) { + return new byte[] { (byte)length }; + } + + /* Long form: determine how many bytes needed */ + tempLength = length; + while (tempLength > 0) { + numLengthBytes++; + tempLength >>= 8; + } + + /* First byte: 0x80 | numLengthBytes */ + encoded = new byte[1 + numLengthBytes]; + encoded[0] = (byte)(0x80 | numLengthBytes); + + /* Following bytes: length in big-endian */ + for (int i = 0; i < numLengthBytes; i++) { + encoded[1 + i] = (byte)(length >> ((numLengthBytes - 1 - i) * 8)); + } + + return encoded; + } + + /** + * Get the DER-encoded DH algorithm OID. + * + * Returns: 1.2.840.113549.1.3.1 (PKCS #3 DH) + * + * @return DER-encoded OBJECT IDENTIFIER for DH algorithm + */ + public static byte[] getDHAlgorithmOID() { + + return DH_ALGORITHM_OID.clone(); + } + + /** + * Encode DH parameters (p, g) as a DER SEQUENCE. + * + * Structure: + * SEQUENCE { + * p INTEGER + * g INTEGER + * } + * + * @param p the prime modulus + * @param g the generator + * + * @return DER-encoded parameter SEQUENCE + * + * @throws IllegalArgumentException if p or g is null + */ + public static byte[] encodeDHParameters(BigInteger p, BigInteger g) + throws IllegalArgumentException { + + ByteArrayOutputStream out; + + if (p == null || g == null) { + throw new IllegalArgumentException( + "DH parameters p and g cannot be null"); + } + + out = new ByteArrayOutputStream(); + + try { + out.write(encodeDERInteger(p)); + out.write(encodeDERInteger(g)); + + return encodeDERSequence(out.toByteArray()); + + } catch (IOException e) { + throw new IllegalArgumentException( + "Failed to encode DH parameters: " + e.getMessage(), e); + } + } + + /** + * Encode DH AlgorithmIdentifier with parameters. + * + * Structure: + * SEQUENCE { + * algorithm OBJECT IDENTIFIER (DH OID) + * parameters SEQUENCE { p INTEGER, g INTEGER } + * } + * + * @param p the prime modulus + * @param g the generator + * + * @return DER-encoded AlgorithmIdentifier + * + * @throws IllegalArgumentException if p or g is null + */ + public static byte[] encodeDHAlgorithmIdentifier(BigInteger p, BigInteger g) + throws IllegalArgumentException { + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + out.write(getDHAlgorithmOID()); + out.write(encodeDHParameters(p, g)); + + return encodeDERSequence(out.toByteArray()); + + } catch (IOException e) { + throw new IllegalArgumentException( + "Failed to encode AlgorithmIdentifier: " + + e.getMessage(), e); + } + } + + /** + * Decode DER length from encoded bytes. + * + * Reads DER length encoding at the specified index and returns the + * decoded length value. + * + * DER length encoding: + * - Short form (length less than 128): one byte with value + * - Long form (length greater than or equal to 128): first byte has + * bit 7 set and lower 7 bits indicate number of following length + * bytes, then length bytes in big-endian order + * + * @param data DER-encoded data + * @param idx index where length encoding starts + * + * @return decoded length value + * + * @throws IllegalArgumentException if data is null or index is invalid + * @throws ArrayIndexOutOfBoundsException if data is too short + */ + public static int getDERLength(byte[] data, int idx) + throws IllegalArgumentException { + + int len, numBytes, result; + + if (data == null) { + throw new IllegalArgumentException("Data cannot be null"); + } + if (idx < 0 || idx >= data.length) { + throw new IllegalArgumentException("Invalid index: " + idx); + } + + len = data[idx] & 0xFF; + + if ((len & 0x80) == 0) { + /* Short form */ + return len; + } + + /* Long form */ + numBytes = len & 0x7F; + result = 0; + for (int i = 0; i < numBytes; i++) { + result = (result << 8) | (data[idx + 1 + i] & 0xFF); + } + + return result; + } + + /** + * Get size of DER length encoding at specified index. + * + * Returns the number of bytes used to encode the length value + * at the given index. + * + * @param data DER-encoded data + * @param idx index where length encoding starts + * + * @return size of length encoding in bytes (1 for short form, + * 1 + n for long form) + * + * @throws IllegalArgumentException if data is null or index is invalid + */ + public static int getDERLengthSize(byte[] data, int idx) + throws IllegalArgumentException { + + int len; + + if (data == null) { + throw new IllegalArgumentException("Data cannot be null"); + } + if (idx < 0 || idx >= data.length) { + throw new IllegalArgumentException("Invalid index: " + idx); + } + + len = data[idx] & 0xFF; + + if ((len & 0x80) == 0) { + /* Short form - 1 byte */ + return 1; + } + + /* Long form - 1 + number of length bytes */ + return 1 + (len & 0x7F); + } + + /** + * Convert BigInteger to byte array, removing leading zero if present. + * + * BigInteger.toByteArray() returns a two's complement representation, + * which includes a leading zero byte if the most significant bit is + * set (to distinguish positive from negative). This method removes + * that leading zero byte if present. + * + * @param value BigInteger to convert + * + * @return byte array representation without unnecessary leading zero + * + * @throws IllegalArgumentException if value is null + */ + public static byte[] bigIntegerToByteArray(BigInteger value) + throws IllegalArgumentException { + + byte[] bytes, tmp; + + if (value == null) { + throw new IllegalArgumentException("Value cannot be null"); + } + + bytes = value.toByteArray(); + + /* Remove leading zero byte if present (sign bit padding) */ + if (bytes.length > 1 && bytes[0] == 0) { + tmp = new byte[bytes.length - 1]; + System.arraycopy(bytes, 1, tmp, 0, tmp.length); + return tmp; + } + + return bytes; + } +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHKeyFactory.java new file mode 100644 index 00000000..22b3dc68 --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHKeyFactory.java @@ -0,0 +1,592 @@ +/* WolfCryptDHKeyFactory.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 javax.crypto.interfaces.DHPrivateKey; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.DHPrivateKeySpec; +import javax.crypto.spec.DHPublicKeySpec; + +import com.wolfssl.wolfcrypt.Dh; +import com.wolfssl.wolfcrypt.WolfCryptException; + +/** + * wolfCrypt JCE DH KeyFactory implementation. + * + * This class provides key conversion capabilities for Diffie-Hellman (DH) + * keys, supporting conversion between various KeySpec formats and Key objects. + */ +public class WolfCryptDHKeyFactory extends KeyFactorySpi { + + /** + * Create new WolfCryptDHKeyFactory object. + */ + public WolfCryptDHKeyFactory() { + log("created new DH KeyFactory"); + } + + /** + * Internal method for logging output. + * + * @param msg message to be logged + */ + private void log(String msg) { + WolfCryptDebug.log(getClass(), WolfCryptDebug.INFO, + () -> "[DH 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 DHPrivateKeySpec + */ + @Override + protected PrivateKey engineGeneratePrivate(KeySpec keySpec) + throws InvalidKeySpecException { + + log("generating DHPrivateKey from KeySpec"); + + if (keySpec == null) { + throw new InvalidKeySpecException("KeySpec cannot be null"); + } + + if (keySpec instanceof PKCS8EncodedKeySpec) { + return generatePrivateFromPKCS8((PKCS8EncodedKeySpec)keySpec); + } + else if (keySpec instanceof DHPrivateKeySpec) { + return generatePrivateFromDHSpec((DHPrivateKeySpec)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 DHPublicKeySpec. + */ + @Override + protected PublicKey engineGeneratePublic(KeySpec keySpec) + throws InvalidKeySpecException { + + log("generating DHPublicKey from KeySpec"); + + if (keySpec == null) { + throw new InvalidKeySpecException("KeySpec cannot be null"); + } + + if (keySpec instanceof X509EncodedKeySpec) { + return generatePublicFromX509((X509EncodedKeySpec)keySpec); + } + else if (keySpec instanceof DHPublicKeySpec) { + return generatePublicFromDHSpec((DHPublicKeySpec)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 DHPrivateKey or DHPublicKey + * @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 DHPrivateKey) { + return getPrivateKeySpec((DHPrivateKey)key, keySpec); + } + else if (key instanceof DHPublicKey) { + return getPublicKeySpec((DHPublicKey)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 DHPrivateKey + * or DHPublicKey + * + * @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 DH KeyFactory type"); + + if (key == null) { + throw new InvalidKeyException("Key cannot be null"); + } + + if (key instanceof DHPrivateKey) { + return translatePrivateKey((DHPrivateKey)key); + } + else if (key instanceof DHPublicKey) { + return translatePublicKey((DHPublicKey)key); + } + else { + throw new InvalidKeyException( + "Unsupported Key type: " + key.getClass().getName()); + } + } + + /** + * Private helper method for generating DHPrivateKey from + * PKCS8EncodedKeySpec. + * + * @param keySpec the PKCS8EncodedKeySpec containing the private key + * + * @return the generated DHPrivateKey + * + * @throws InvalidKeySpecException if the key specification is invalid + */ + private PrivateKey generatePrivateFromPKCS8(PKCS8EncodedKeySpec keySpec) + throws InvalidKeySpecException { + + if (keySpec == null) { + throw new InvalidKeySpecException( + "PKCS8EncodedKeySpec cannot be null"); + } + + /* Get DER-encoded PKCS#8 data from spec */ + byte[] pkcs8Der = keySpec.getEncoded(); + if (pkcs8Der == null) { + throw new InvalidKeySpecException( + "PKCS8EncodedKeySpec contains null encoded key"); + } + + log("generating DHPrivateKey from PKCS8EncodedKeySpec, length: " + + pkcs8Der.length); + + try { + /* Create wolfJCE DHPrivateKey object directly from PKCS#8 DER. + * WolfCryptDHPrivateKey will parse the PKCS#8 DER in Java, + * which works with all wolfSSL builds including FIPS without + * requiring WOLFSSL_DH_EXTRA. */ + return new WolfCryptDHPrivateKey(pkcs8Der); + + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException( + "Invalid PKCS#8 key data: " + e.getMessage(), e); + } + } + + /** + * Private helper method for generating DHPrivateKey from + * DHPrivateKeySpec. + * + * This method creates the key directly using WolfCryptDHPrivateKey, + * which generates PKCS#8 DER in pure Java to support wolfCrypt FIPS + * builds that do not define WOLFSSL_DH_EXTRA. + * + * If WOLFSSL_DH_EXTRA were available, we could use: + * - wc_DhImportKeyPair() to import the key + * - wc_DhPrivKeyToDer() to encode as PKCS#8 + * + * @param keySpec the DHPrivateKeySpec containing the private key + * + * @return the generated DHPrivateKey + * + * @throws InvalidKeySpecException if the key specification is invalid + */ + private PrivateKey generatePrivateFromDHSpec(DHPrivateKeySpec keySpec) + throws InvalidKeySpecException { + + log("generating DHPrivateKey from DHPrivateKeySpec"); + + if (keySpec == null) { + throw new InvalidKeySpecException( + "DHPrivateKeySpec cannot be null"); + } + if (keySpec.getX() == null) { + throw new InvalidKeySpecException( + "Private key value cannot be null"); + } + if (keySpec.getP() == null) { + throw new InvalidKeySpecException( + "Parameter P cannot be null"); + } + if (keySpec.getG() == null) { + throw new InvalidKeySpecException( + "Parameter G cannot be null"); + } + + /* Validate private key is positive */ + if (keySpec.getX().signum() <= 0) { + throw new InvalidKeySpecException( + "Private key value must be positive"); + } + + try { + /* Create DHParameterSpec from p and g */ + DHParameterSpec paramSpec = new DHParameterSpec( + keySpec.getP(), keySpec.getG()); + + /* Create WolfCryptDHPrivateKey directly - it will generate + * PKCS#8 DER using pure Java (no WOLFSSL_DH_EXTRA needed) */ + return new WolfCryptDHPrivateKey(keySpec.getX(), paramSpec); + + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException( + "Failed to create DHPrivateKey from spec: " + + e.getMessage(), e); + } + } + + /** + * Private helper method for generating DHPublicKey from + * X509EncodedKeySpec. + * + * @param keySpec the X509EncodedKeySpec containing the public key + * + * @return the generated DHPublicKey + * + * @throws InvalidKeySpecException if the key specification is invalid + */ + private PublicKey generatePublicFromX509(X509EncodedKeySpec keySpec) + throws InvalidKeySpecException { + + if (keySpec == null) { + throw new InvalidKeySpecException( + "X509EncodedKeySpec cannot be null"); + } + + /* Get DER-encoded X.509 data from spec */ + byte[] x509Der = keySpec.getEncoded(); + if (x509Der == null) { + throw new InvalidKeySpecException( + "X509EncodedKeySpec contains null encoded key"); + } + + log("generating DHPublicKey from X509EncodedKeySpec, length: " + + x509Der.length); + + try { + return new WolfCryptDHPublicKey(x509Der); + + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException( + "Invalid X.509 key data: " + e.getMessage(), e); + } + } + + /** + * Private helper method for generating DHPublicKey from DHPublicKeySpec. + * + * This method creates the key directly using WolfCryptDHPublicKey, + * which generates X.509 DER in pure Java to support wolfCrypt FIPS + * builds that do not define WOLFSSL_DH_EXTRA. + * + * If WOLFSSL_DH_EXTRA were available, we could use: + * - wc_DhImportKeyPair() to import the key + * - wc_DhPubKeyToDer() to encode as DER + * + * @param keySpec the DHPublicKeySpec containing the public key + * + * @return the generated DHPublicKey + * + * @throws InvalidKeySpecException if the key specification is invalid + */ + private PublicKey generatePublicFromDHSpec(DHPublicKeySpec keySpec) + throws InvalidKeySpecException { + + log("generating DHPublicKey from DHPublicKeySpec"); + + if (keySpec == null) { + throw new InvalidKeySpecException( + "DHPublicKeySpec cannot be null"); + } + if (keySpec.getY() == null) { + throw new InvalidKeySpecException( + "Public key value cannot be null"); + } + if (keySpec.getP() == null) { + throw new InvalidKeySpecException( + "Parameter P cannot be null"); + } + if (keySpec.getG() == null) { + throw new InvalidKeySpecException( + "Parameter G cannot be null"); + } + + /* Validate public key is positive */ + if (keySpec.getY().signum() <= 0) { + throw new InvalidKeySpecException( + "Public key value must be positive"); + } + + try { + /* Create DHParameterSpec from p and g */ + DHParameterSpec paramSpec = new DHParameterSpec( + keySpec.getP(), keySpec.getG()); + + return new WolfCryptDHPublicKey(keySpec.getY(), paramSpec); + + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException( + "Failed to create DHPublicKey from spec: " + + e.getMessage(), e); + } + } + + /** + * Private helper methods for extracting KeySpec from Key. + * + * @param the type of KeySpec to be returned + * @param key the DHPrivateKey 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(DHPrivateKey key, + Class keySpec) throws InvalidKeySpecException { + + try { + if (key == null) { + throw new InvalidKeySpecException( + "DHPrivateKey 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( + "DHPrivateKey.getEncoded() returned null"); + } + return (T) new PKCS8EncodedKeySpec(encoded); + } + else if (keySpec.isAssignableFrom(DHPrivateKeySpec.class)) { + /* Extract private value and params directly from key */ + return (T) new DHPrivateKeySpec(key.getX(), + key.getParams().getP(), key.getParams().getG()); + } + 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 DHPublicKey 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(DHPublicKey key, + Class keySpec) throws InvalidKeySpecException { + + byte[] encoded; + + try { + if (key == null) { + throw new InvalidKeySpecException( + "DHPublicKey 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)) { + encoded = key.getEncoded(); + if (encoded == null) { + throw new InvalidKeySpecException( + "DHPublicKey.getEncoded() returned null"); + } + return (T) new X509EncodedKeySpec(encoded); + } + else if (keySpec.isAssignableFrom(DHPublicKeySpec.class)) { + /* Extract public value and parameters directly from key */ + return (T) new DHPublicKeySpec(key.getY(), + key.getParams().getP(), key.getParams().getG()); + } + 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 DHPrivateKey from foreign provider into wolfJCE DHPrivateKey. + * + * @param key the DHPrivateKey to be translated + * + * @return the translated PrivateKey + * + * @throws InvalidKeyException if the key cannot be translated + */ + private PrivateKey translatePrivateKey(DHPrivateKey key) + throws InvalidKeyException { + + byte[] encoded; + PKCS8EncodedKeySpec keySpec; + + try { + log("translating DHPrivateKey from foreign provider"); + + if (key == null) { + throw new InvalidKeyException( + "DHPrivateKey cannot be null"); + } + + /* Get encoded format and convert through our KeyFactory */ + encoded = key.getEncoded(); + if (encoded == null) { + throw new InvalidKeyException( + "DHPrivateKey.getEncoded() returned null"); + } + + keySpec = new PKCS8EncodedKeySpec(encoded); + return engineGeneratePrivate(keySpec); + + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to translate DHPrivateKey: " + e.getMessage(), e); + } + } + + /** + * Translate DHPublicKey from foreign provider into wolfJCE DHPublicKey. + * + * @param key the DHPublicKey to be translated + * + * @return the translated PublicKey + * + * @throws InvalidKeyException if the key cannot be translated + */ + private PublicKey translatePublicKey(DHPublicKey key) + throws InvalidKeyException { + + byte[] encoded; + X509EncodedKeySpec keySpec; + + try { + log("translating DHPublicKey from foreign provider"); + + if (key == null) { + throw new InvalidKeyException( + "DHPublicKey cannot be null"); + } + + /* Get encoded format and convert through our KeyFactory */ + encoded = key.getEncoded(); + if (encoded == null) { + throw new InvalidKeyException( + "DHPublicKey.getEncoded() returned null"); + } + + keySpec = new X509EncodedKeySpec(encoded); + return engineGeneratePublic(keySpec); + + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to translate DHPublicKey: " + e.getMessage(), e); + } + } +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java new file mode 100644 index 00000000..aa22175d --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java @@ -0,0 +1,466 @@ +/* WolfCryptDHPrivateKey.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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Arrays; + +import javax.crypto.interfaces.DHPrivateKey; +import javax.crypto.spec.DHParameterSpec; + +import com.wolfssl.wolfcrypt.Dh; +import com.wolfssl.wolfcrypt.WolfCryptException; + +/** + * wolfJCE DHPrivateKey implementation. + * This class implements the DHPrivateKey interface using wolfCrypt. + */ +public class WolfCryptDHPrivateKey implements DHPrivateKey { + + private static final long serialVersionUID = 1L; + + /** DER-encoded private key (PKCS#8 format) */ + private byte[] encoded = null; + + /** Cached DHParameterSpec, extracted on first access */ + private transient DHParameterSpec 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 WolfCryptDHPrivateKey from DER-encoded PKCS#8 data. + * + * @param encoded DER-encoded PKCS#8 private key + * + * @throws IllegalArgumentException if encoded data is null or invalid + */ + public WolfCryptDHPrivateKey(byte[] encoded) + throws IllegalArgumentException { + + if (encoded == null || encoded.length == 0) { + throw new IllegalArgumentException( + "Encoded key data cannot be null or empty"); + } + + /* Parse PKCS#8 DER to extract private value and parameters */ + parsePKCS8Der(encoded); + + /* Store a copy of the encoded data */ + this.encoded = encoded.clone(); + } + + /** + * Create new WolfCryptDHPrivateKey from private value and parameters. + * + * @param privateValue the private key value (x) + * @param paramSpec the DH parameters + * + * @throws IllegalArgumentException if parameters are invalid + */ + public WolfCryptDHPrivateKey(BigInteger privateValue, + DHParameterSpec paramSpec) throws IllegalArgumentException { + + if (privateValue == null) { + throw new IllegalArgumentException( + "Private value cannot be null"); + } + if (paramSpec == null) { + throw new IllegalArgumentException( + "DHParameterSpec cannot be null"); + } + + /* Store params */ + this.privateValue = privateValue; + this.paramSpec = paramSpec; + + /* Generate PKCS#8 DER-encoded form using pure Java */ + this.encoded = generatePKCS8Der(); + } + + /** + * Parse PKCS#8 DER structure to extract private value and parameters. + * + * We are doing this in Java for now since wolfCrypt functionality to + * decode PKCS#8 DER and extract DH parameters requires WOLFSSL_DH_EXTRA, + * which is not defined for wolfCrypt FIPS bundles (one of the primary + * user categories of wolfJCE). + * + * PKCS#8 DH structure: + * SEQUENCE { + * version INTEGER + * SEQUENCE { + * algorithm OID + * parameters SEQUENCE { p INTEGER, g INTEGER } + * } + * privateKey OCTET STRING { INTEGER } + * } + * + * + * If wolfCrypt FIPS does define WOLFSSL_DH_EXTRA, the following APIs + * could be used instead of manual parsing here: + * wc_InitDhKey() + * wc_DhKeyDecode() + * wc_DhExportParamsRaw() + * wc_DhExportKeyPair() + * + * @param derData DER-encoded PKCS#8 private key + * + * @throws IllegalArgumentException if DER data is invalid + */ + private void parsePKCS8Der(byte[] derData) + throws IllegalArgumentException { + + int idx = 0; + int outerSeqLen, algSeqLen, paramsSeqLen; + int versionLen, oidLen, pLen, gLen, octetLen, privLen; + byte[] pBytes, gBytes, privBytes; + BigInteger p, g, privateVal; + + try { + + /* Outer SEQUENCE */ + if (derData[idx++] != 0x30) { + throw new IllegalArgumentException( + "Invalid PKCS#8: expected SEQUENCE tag"); + } + outerSeqLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + + /* Version INTEGER (should be 0) */ + if (derData[idx++] != 0x02) { + throw new IllegalArgumentException( + "Invalid PKCS#8: expected version INTEGER"); + } + versionLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + idx += versionLen; /* Skip version value */ + + /* AlgorithmIdentifier SEQUENCE */ + if (derData[idx++] != 0x30) { + throw new IllegalArgumentException( + "Invalid PKCS#8: expected AlgorithmIdentifier SEQUENCE"); + } + algSeqLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + + /* Algorithm OID - skip it */ + if (derData[idx++] != 0x06) { + throw new IllegalArgumentException( + "Invalid PKCS#8: expected algorithm OID"); + } + oidLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + idx += oidLen; + + /* DH Parameters SEQUENCE { p, g } */ + if (derData[idx++] != 0x30) { + throw new IllegalArgumentException( + "Invalid PKCS#8: expected DH parameters SEQUENCE"); + } + paramsSeqLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + + /* p INTEGER */ + if (derData[idx++] != 0x02) { + throw new IllegalArgumentException( + "Invalid PKCS#8: expected p INTEGER"); + } + pLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + pBytes = new byte[pLen]; + System.arraycopy(derData, idx, pBytes, 0, pLen); + p = new BigInteger(1, pBytes); + idx += pLen; + + /* g INTEGER */ + if (derData[idx++] != 0x02) { + throw new IllegalArgumentException( + "Invalid PKCS#8: expected g INTEGER"); + } + gLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + gBytes = new byte[gLen]; + System.arraycopy(derData, idx, gBytes, 0, gLen); + g = new BigInteger(1, gBytes); + idx += gLen; + + /* PrivateKey OCTET STRING */ + if (derData[idx++] != 0x04) { + throw new IllegalArgumentException( + "Invalid PKCS#8: expected privateKey OCTET STRING"); + } + octetLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + + /* Private key value is an INTEGER inside the OCTET STRING */ + if (derData[idx++] != 0x02) { + throw new IllegalArgumentException( + "Invalid PKCS#8: expected private value INTEGER"); + } + privLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + privBytes = new byte[privLen]; + System.arraycopy(derData, idx, privBytes, 0, privLen); + privateVal = new BigInteger(1, privBytes); + + /* Store extracted values */ + this.privateValue = privateVal; + this.paramSpec = new DHParameterSpec(p, g); + + log("parsed PKCS#8 DER: p.bitLength=" + p.bitLength() + + ", g=" + g + ", priv.bitLength=" + privateVal.bitLength()); + + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException( + "Invalid PKCS#8 encoding: " + e.getMessage(), e); + + } catch (Exception e) { + throw new IllegalArgumentException( + "Failed to parse PKCS#8: " + e.getMessage(), e); + } + } + + /** + * Generate PKCS#8 DER-encoded form from private value and parameters. + * + * This method generates the DER encoding in pure Java to support + * wolfCrypt FIPS builds that do not define WOLFSSL_DH_EXTRA. + * + * If WOLFSSL_DH_EXTRA were available, we could use these native APIs: + * - wc_DhImportKeyPair() to import the key + * - wc_DhPrivKeyToDer() to encode as PKCS#8 + * + * PKCS#8 DH PrivateKeyInfo structure: + * SEQUENCE { + * version INTEGER (0) + * AlgorithmIdentifier SEQUENCE { + * algorithm OBJECT IDENTIFIER (1.2.840.113549.1.3.1) + * parameters SEQUENCE { p INTEGER, g INTEGER } + * } + * privateKey OCTET STRING { + * INTEGER (private value) + * } + * } + * + * @return DER-encoded PKCS#8 private key + * + * @throws IllegalArgumentException if key generation fails + */ + private byte[] generatePKCS8Der() + throws IllegalArgumentException { + + byte[] privKeyInteger; + byte[] pkcs8Der; + + try { + log("generating PKCS#8 DER manually from private value and " + + "DHParameterSpec"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + /* Version: INTEGER 0 */ + out.write(WolfCryptASN1Util.encodeDERInteger(BigInteger.ZERO)); + + /* AlgorithmIdentifier: SEQUENCE { OID, parameters } */ + out.write(WolfCryptASN1Util.encodeDHAlgorithmIdentifier( + this.paramSpec.getP(), this.paramSpec.getG())); + + /* PrivateKey: OCTET STRING containing INTEGER */ + privKeyInteger = + WolfCryptASN1Util.encodeDERInteger(this.privateValue); + out.write(WolfCryptASN1Util.encodeDEROctetString(privKeyInteger)); + + /* Wrap everything in outer SEQUENCE */ + pkcs8Der = WolfCryptASN1Util.encodeDERSequence(out.toByteArray()); + + log("successfully generated PKCS#8 DER manually, length: " + + pkcs8Der.length); + + return pkcs8Der; + + } catch (IOException e) { + throw new IllegalArgumentException( + "Failed to generate PKCS#8 DER: " + e.getMessage(), e); + } + } + + /** + * Internal method for logging output. + * + * @param msg message to be logged + */ + private void log(String msg) { + WolfCryptDebug.log(getClass(), WolfCryptDebug.INFO, + () -> "[WolfCryptDHPrivateKey] " + msg); + } + + @Override + public BigInteger getX() { + + synchronized (stateLock) { + if (destroyed) { + throw new IllegalStateException("Key has been destroyed"); + } + return privateValue; + } + } + + @Override + public DHParameterSpec getParams() { + + synchronized (stateLock) { + if (destroyed) { + throw new IllegalStateException("Key has been destroyed"); + } + return paramSpec; + } + } + + @Override + public String getAlgorithm() { + return "DH"; + } + + @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 DHPrivateKey)) { + return false; + } + + DHPrivateKey other = (DHPrivateKey) obj; + + synchronized (stateLock) { + if (destroyed) { + return false; + } + + /* Compare encoded forms if both are WolfCryptDHPrivateKey */ + if (obj instanceof WolfCryptDHPrivateKey) { + WolfCryptDHPrivateKey otherWolf = + (WolfCryptDHPrivateKey) obj; + + synchronized (otherWolf.stateLock) { + if (otherWolf.destroyed) { + return false; + } + return Arrays.equals(this.encoded, otherWolf.encoded); + } + } + + /* Compare with other DHPrivateKey implementations */ + try { + if (getX().equals(other.getX()) && + getParams().equals(other.getParams())) { + return true; + + } else { + return false; + } + + } catch (Exception e) { + return false; + } + } + } + + @Override + public String toString() { + synchronized (stateLock) { + if (destroyed) { + return "WolfCryptDHPrivateKey[DESTROYED]"; + } + return "WolfCryptDHPrivateKey[algorithm=DH, format=PKCS#8, " + + "encoded.length=" + encoded.length + "]"; + } + } +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java new file mode 100644 index 00000000..a7d0bc2f --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java @@ -0,0 +1,456 @@ +/* WolfCryptDHPublicKey.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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Arrays; + +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; + +import com.wolfssl.wolfcrypt.Dh; +import com.wolfssl.wolfcrypt.WolfCryptException; + +/** + * wolfJCE DHPublicKey implementation. + * This class implements the DHPublicKey interface using wolfCrypt. + */ +public class WolfCryptDHPublicKey implements DHPublicKey { + + private static final long serialVersionUID = 1L; + + /** DER-encoded public key (X.509 format) */ + private byte[] encoded = null; + + /** Cached DHParameterSpec, extracted on first access */ + private transient DHParameterSpec paramSpec = null; + + /** Cached public key value, extracted on first access */ + private transient BigInteger publicValue = 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 WolfCryptDHPublicKey from DER-encoded X.509 data. + * + * @param encoded DER-encoded X.509 public key + * + * @throws IllegalArgumentException if encoded data is null or invalid + */ + public WolfCryptDHPublicKey(byte[] encoded) + throws IllegalArgumentException { + + if (encoded == null || encoded.length == 0) { + throw new IllegalArgumentException( + "Encoded key data cannot be null or empty"); + } + + /* Parse X.509 DER to extract public value and parameters */ + parseX509Der(encoded); + + /* Store a copy of the encoded data */ + this.encoded = encoded.clone(); + } + + /** + * Create new WolfCryptDHPublicKey from public value and parameters. + * + * @param publicValue the public key value (y) + * @param paramSpec the DH parameters + * + * @throws IllegalArgumentException if parameters are invalid + */ + public WolfCryptDHPublicKey(BigInteger publicValue, + DHParameterSpec paramSpec) throws IllegalArgumentException { + + if (publicValue == null) { + throw new IllegalArgumentException( + "Public value cannot be null"); + } + if (paramSpec == null) { + throw new IllegalArgumentException( + "DHParameterSpec cannot be null"); + } + + /* Store params */ + this.publicValue = publicValue; + this.paramSpec = paramSpec; + + /* Generate X.509 DER-encoded form using pure Java */ + this.encoded = generateX509Der(); + } + + /** + * Parse X.509 DER structure to extract public value and parameters. + * + * We are doing this in Java for now since wolfCrypt functionality to + * decode DH parameters requires WOLFSSL_DH_EXTRA, which is not defined + * for wolfCrypt FIPS bundles (one of the primary user categories of + * wolfJCE). + * + * X.509 DH structure: + * SEQUENCE { + * SEQUENCE { + * algorithm OID + * parameters SEQUENCE { p INTEGER, g INTEGER } + * } + * publicKey BIT STRING { INTEGER } + * } + * + * If wolfCrypt FIPS does define WOLFSSL_DH_EXTRA, the following APIs + * could be used instead of manual parsing here: + * wc_InitDecodedCert() + * wc_ParseCert() + * wc_GetPubKeyDerFromCert() + * wc_DhPublicKeyDecode() + * wc_DhExportParamsRaw() + * wc_DhExportKeyPair() + * + * @param derData DER-encoded X.509 public key + * + * @throws IllegalArgumentException if DER data is invalid + */ + private void parseX509Der(byte[] derData) + throws IllegalArgumentException { + + int idx = 0; + int outerSeqLen, algSeqLen, paramsSeqLen; + int oidLen, pLen, gLen, bitStringLen, pubLen; + byte[] pBytes, gBytes, pubBytes; + BigInteger p, g, publicVal; + + try { + + /* Outer SEQUENCE */ + if (derData[idx++] != 0x30) { + throw new IllegalArgumentException( + "Invalid X.509: expected SEQUENCE tag"); + } + outerSeqLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + + /* AlgorithmIdentifier SEQUENCE */ + if (derData[idx++] != 0x30) { + throw new IllegalArgumentException( + "Invalid X.509: expected AlgorithmIdentifier SEQUENCE"); + } + algSeqLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + + /* Algorithm OID - skip it */ + if (derData[idx++] != 0x06) { + throw new IllegalArgumentException( + "Invalid X.509: expected algorithm OID"); + } + oidLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + idx += oidLen; + + /* DH Parameters SEQUENCE { p, g } */ + if (derData[idx++] != 0x30) { + throw new IllegalArgumentException( + "Invalid X.509: expected DH parameters SEQUENCE"); + } + paramsSeqLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + + /* p INTEGER */ + if (derData[idx++] != 0x02) { + throw new IllegalArgumentException( + "Invalid X.509: expected p INTEGER"); + } + pLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + pBytes = new byte[pLen]; + System.arraycopy(derData, idx, pBytes, 0, pLen); + p = new BigInteger(1, pBytes); + idx += pLen; + + /* g INTEGER */ + if (derData[idx++] != 0x02) { + throw new IllegalArgumentException( + "Invalid X.509: expected g INTEGER"); + } + gLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + gBytes = new byte[gLen]; + System.arraycopy(derData, idx, gBytes, 0, gLen); + g = new BigInteger(1, gBytes); + idx += gLen; + + /* PublicKey BIT STRING */ + if (derData[idx++] != 0x03) { + throw new IllegalArgumentException( + "Invalid X.509: expected publicKey BIT STRING"); + } + bitStringLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + + /* Skip unused bits byte (should be 0) */ + idx++; + + /* Public key value is an INTEGER inside the BIT STRING */ + if (derData[idx++] != 0x02) { + throw new IllegalArgumentException( + "Invalid X.509: expected public value INTEGER"); + } + pubLen = WolfCryptASN1Util.getDERLength(derData, idx); + idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + pubBytes = new byte[pubLen]; + System.arraycopy(derData, idx, pubBytes, 0, pubLen); + publicVal = new BigInteger(1, pubBytes); + + /* Store extracted values */ + this.publicValue = publicVal; + this.paramSpec = new DHParameterSpec(p, g); + + log("parsed X.509 DER: p.bitLength=" + p.bitLength() + + ", g=" + g + ", pub.bitLength=" + publicVal.bitLength()); + + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException( + "Invalid X.509 encoding: " + e.getMessage(), e); + + } catch (Exception e) { + throw new IllegalArgumentException( + "Failed to parse X.509: " + e.getMessage(), e); + } + } + + /** + * Generate X.509 DER-encoded form from public value and parameters. + * + * This method generates the DER encoding in pure Java to support + * wolfCrypt FIPS builds that do not define WOLFSSL_DH_EXTRA. + * + * If WOLFSSL_DH_EXTRA were available, we could use these native APIs: + * - wc_DhImportKeyPair() to import the key + * - wc_DhPubKeyToDer() to encode as X.509 + * + * X.509 DH SubjectPublicKeyInfo structure: + * SEQUENCE { + * AlgorithmIdentifier SEQUENCE { + * algorithm OBJECT IDENTIFIER (1.2.840.113549.1.3.1) + * parameters SEQUENCE { p INTEGER, g INTEGER } + * } + * publicKey BIT STRING { + * unused_bits (0x00) + * INTEGER (public value) + * } + * } + * + * @return DER-encoded X.509 public key + * + * @throws IllegalArgumentException if key generation fails + */ + private byte[] generateX509Der() + throws IllegalArgumentException { + + byte[] pubKeyInteger; + byte[] x509Der; + + try { + log("generating X.509 DER manually from public value and " + + "DHParameterSpec"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + /* AlgorithmIdentifier: SEQUENCE { OID, parameters } */ + out.write(WolfCryptASN1Util.encodeDHAlgorithmIdentifier( + this.paramSpec.getP(), this.paramSpec.getG())); + + /* PublicKey: BIT STRING containing INTEGER */ + pubKeyInteger = + WolfCryptASN1Util.encodeDERInteger(this.publicValue); + out.write(WolfCryptASN1Util.encodeDERBitString(pubKeyInteger)); + + /* Wrap everything in outer SEQUENCE */ + x509Der = WolfCryptASN1Util.encodeDERSequence(out.toByteArray()); + + log("successfully generated X.509 DER manually, length: " + + x509Der.length); + + return x509Der; + + } catch (IOException e) { + throw new IllegalArgumentException( + "Failed to generate X.509 DER: " + e.getMessage(), e); + } + } + + /** + * Internal method for logging output. + * + * @param msg message to be logged + */ + private void log(String msg) { + WolfCryptDebug.log(getClass(), WolfCryptDebug.INFO, + () -> "[WolfCryptDHPublicKey] " + msg); + } + + @Override + public BigInteger getY() { + + synchronized (stateLock) { + if (destroyed) { + throw new IllegalStateException("Key has been destroyed"); + } + return publicValue; + } + } + + @Override + public DHParameterSpec getParams() { + + synchronized (stateLock) { + if (destroyed) { + throw new IllegalStateException("Key has been destroyed"); + } + return paramSpec; + } + } + + @Override + public String getAlgorithm() { + return "DH"; + } + + @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); + } + publicValue = 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 DHPublicKey)) { + return false; + } + + DHPublicKey other = (DHPublicKey) obj; + + synchronized (stateLock) { + if (destroyed) { + return false; + } + + /* Compare encoded forms if both are WolfCryptDHPublicKey */ + if (obj instanceof WolfCryptDHPublicKey) { + WolfCryptDHPublicKey otherWolf = (WolfCryptDHPublicKey) obj; + + synchronized (otherWolf.stateLock) { + if (otherWolf.destroyed) { + return false; + } + return Arrays.equals(this.encoded, otherWolf.encoded); + } + } + + /* Compare with other DHPublicKey implementations */ + try { + if (getY().equals(other.getY()) && + getParams().equals(other.getParams())) { + return true; + + } else { + return false; + } + + } catch (Exception e) { + return false; + } + } + } + + @Override + public String toString() { + synchronized (stateLock) { + if (destroyed) { + return "WolfCryptDHPublicKey[DESTROYED]"; + } + return "WolfCryptDHPublicKey[algorithm=DH, format=X.509, " + + "encoded.length=" + encoded.length + "]"; + } + } +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptDhParameterGenerator.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptDhParameterGenerator.java new file mode 100644 index 00000000..284d3507 --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptDhParameterGenerator.java @@ -0,0 +1,189 @@ +/* WolfCryptDhParameterGenerator.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.AlgorithmParameterGeneratorSpi; +import java.security.InvalidAlgorithmParameterException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.spec.DHGenParameterSpec; +import javax.crypto.spec.DHParameterSpec; + +import com.wolfssl.wolfcrypt.Dh; +import com.wolfssl.wolfcrypt.Rng; +import com.wolfssl.wolfcrypt.WolfCryptException; + +/** + * wolfCrypt JCE DH AlgorithmParameterGenerator implementation + */ +public class WolfCryptDhParameterGenerator + extends AlgorithmParameterGeneratorSpi { + + /* Default size for DH parameters (bits) */ + private static final int DEFAULT_SIZE = 2048; + + /* Size of DH parameters to generate (bits) */ + private int size = DEFAULT_SIZE; + + /* Exponent size in bits, 0 means not specified */ + private int exponentSize = 0; + + /* SecureRandom for parameter generation */ + private SecureRandom random = null; + + /** + * Create new WolfCryptDhParameterGenerator object + */ + public WolfCryptDhParameterGenerator() { + } + + @Override + protected void engineInit(int size, SecureRandom random) { + this.size = size; + this.random = random; + } + + @Override + protected void engineInit(AlgorithmParameterSpec genParamSpec, + SecureRandom random) throws InvalidAlgorithmParameterException { + + if (genParamSpec == null) { + throw new InvalidAlgorithmParameterException( + "genParamSpec cannot be null"); + } + + if (!(genParamSpec instanceof DHGenParameterSpec)) { + throw new InvalidAlgorithmParameterException( + "AlgorithmParameterSpec must be DHGenParameterSpec for " + + "DH parameter generation"); + } + + DHGenParameterSpec dhGenSpec = (DHGenParameterSpec)genParamSpec; + this.size = dhGenSpec.getPrimeSize(); + this.exponentSize = dhGenSpec.getExponentSize(); + this.random = random; + } + + @Override + protected AlgorithmParameters engineGenerateParameters() { + + AlgorithmParameters algParams = null; + byte[][] params = null; + BigInteger p = null; + BigInteger g = null; + + try { + /* Check if this is a standard FFDHE size */ + int namedGroup = -1; + if (size == 2048) { + namedGroup = Dh.WC_FFDHE_2048; + } + else if (size == 3072) { + namedGroup = Dh.WC_FFDHE_3072; + } + else if (size == 4096) { + namedGroup = Dh.WC_FFDHE_4096; + } + else if (size == 6144) { + namedGroup = Dh.WC_FFDHE_6144; + } + else if (size == 8192) { + namedGroup = Dh.WC_FFDHE_8192; + } + + if (namedGroup != -1) { + /* Try to use pre-computed FFDHE parameters for standard + * sizes. If the named group is not available (not compiled + * into wolfSSL), throw an exception. */ + params = Dh.getNamedDhParams(namedGroup); + + /* Check if the requested FFDHE group is available */ + if (params == null || params.length != 2 || + params[0] == null || params[0].length == 0) { + + throw new RuntimeException( + "FFDHE " + size + "-bit group not available in " + + "native wolfSSL library. Only FFDHE groups compiled " + + "into wolfSSL can be used."); + } + } + else { + /* For non-standard sizes, try dynamic parameter generation + * using wc_DhGenerateParams(). */ + Rng rng = null; + try { + /* Create and initialize RNG. */ + rng = new Rng(); + rng.init(); + + /* Generate DH parameters, may throw exception with + * bad function arg if size not supported natively. */ + params = Dh.generateDhParams(rng, size); + } + finally { + if (rng != null) { + rng.free(); + rng.releaseNativeStruct(); + } + } + } + + if (params == null || params.length != 2) { + throw new RuntimeException( + "Failed to generate DH parameters"); + } + + /* Convert byte arrays to BigInteger */ + p = new BigInteger(1, params[0]); + g = new BigInteger(1, params[1]); + + /* Create DHParameterSpec with generated parameters. + * If exponentSize was specified (via DHGenParameterSpec), + * include it in the DHParameterSpec. */ + DHParameterSpec dhSpec; + if (this.exponentSize > 0) { + dhSpec = new DHParameterSpec(p, g, this.exponentSize); + } + else { + dhSpec = new DHParameterSpec(p, g); + } + + /* Create AlgorithmParameters object and initialize it */ + algParams = AlgorithmParameters.getInstance("DH", "wolfJCE"); + algParams.init(dhSpec); + + } catch (WolfCryptException e) { + throw new RuntimeException( + "Failed to generate DH parameters: " + e.getMessage(), e); + + } catch (Exception e) { + throw new RuntimeException( + "Failed to create AlgorithmParameters: " + e.getMessage(), e); + } + + return algParams; + } +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptDhParameters.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptDhParameters.java new file mode 100644 index 00000000..3c9b669d --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptDhParameters.java @@ -0,0 +1,225 @@ +/* WolfCryptDhParameters.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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.AlgorithmParametersSpi; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; + +import javax.crypto.spec.DHParameterSpec; + +/** + * wolfCrypt JCE DH AlgorithmParameters implementation + */ +public class WolfCryptDhParameters extends AlgorithmParametersSpi { + + /* DH parameters */ + private BigInteger p = null; + private BigInteger g = null; + private int l = 0; + + /** + * Create new WolfCryptDhParameters object + */ + public WolfCryptDhParameters() { + } + + @Override + protected void engineInit(AlgorithmParameterSpec paramSpec) + throws InvalidParameterSpecException { + + if (!(paramSpec instanceof DHParameterSpec)) { + throw new InvalidParameterSpecException( + "Expected DHParameterSpec"); + } + + DHParameterSpec dhSpec = (DHParameterSpec)paramSpec; + this.p = dhSpec.getP(); + this.g = dhSpec.getG(); + this.l = dhSpec.getL(); + } + + @Override + protected void engineInit(byte[] params) throws IOException { + + int idx = 0; + int seqLen = 0; + int pLen = 0; + int gLen = 0; + byte[] pBytes = null; + byte[] gBytes = null; + + /* Parse DER-encoded DH parameters. Doing basic DER parsing here + * since wolfCrypt does not have DER parsing support for this + * encoded parameters format. + * + * Format: SEQUENCE { prime INTEGER, generator INTEGER } */ + try { + /* Check SEQUENCE tag */ + if (params[idx++] != 0x30) { + throw new IOException( + "Invalid DH parameters: expected SEQUENCE tag"); + } + + /* Get sequence length */ + seqLen = WolfCryptASN1Util.getDERLength(params, idx); + idx += WolfCryptASN1Util.getDERLengthSize(params, idx); + + /* Decode prime (p) INTEGER */ + if (params[idx++] != 0x02) { + throw new IOException( + "Invalid DH parameters: expected INTEGER tag for p"); + } + pLen = WolfCryptASN1Util.getDERLength(params, idx); + idx += WolfCryptASN1Util.getDERLengthSize(params, idx); + pBytes = new byte[pLen]; + System.arraycopy(params, idx, pBytes, 0, pLen); + idx += pLen; + this.p = new BigInteger(1, pBytes); + + /* Decode generator (g) INTEGER */ + if (params[idx++] != 0x02) { + throw new IOException( + "Invalid DH parameters: expected INTEGER tag for g"); + } + gLen = WolfCryptASN1Util.getDERLength(params, idx); + idx += WolfCryptASN1Util.getDERLengthSize(params, idx); + gBytes = new byte[gLen]; + System.arraycopy(params, idx, gBytes, 0, gLen); + this.g = new BigInteger(1, gBytes); + + /* Private value length not encoded in standard DH params */ + this.l = 0; + + } catch (ArrayIndexOutOfBoundsException e) { + throw new IOException( + "Invalid DH parameters encoding: " + e.getMessage()); + } + } + + @Override + protected void engineInit(byte[] params, String format) + throws IOException { + + if (format != null && !format.equalsIgnoreCase("ASN.1") && + !format.equalsIgnoreCase("DER")) { + throw new IOException( + "Unsupported format: " + format + + ". Only ASN.1/DER is supported"); + } + + engineInit(params); + } + + @Override + protected + T engineGetParameterSpec(Class paramSpec) + throws InvalidParameterSpecException { + + if (paramSpec == null) { + throw new InvalidParameterSpecException( + "paramSpec cannot be null"); + } + + if (!paramSpec.isAssignableFrom(DHParameterSpec.class)) { + throw new InvalidParameterSpecException( + "Only DHParameterSpec is supported"); + } + + if (this.p == null || this.g == null) { + throw new InvalidParameterSpecException( + "Parameters not initialized"); + } + + return paramSpec.cast(new DHParameterSpec(this.p, this.g, this.l)); + } + + @Override + protected byte[] engineGetEncoded() throws IOException { + return engineGetEncoded("ASN.1"); + } + + @Override + protected byte[] engineGetEncoded(String format) throws IOException { + + if (format != null && !format.equalsIgnoreCase("ASN.1") && + !format.equalsIgnoreCase("DER")) { + throw new IOException( + "Unsupported format: " + format + + ". Only ASN.1/DER is supported"); + } + + if (this.p == null || this.g == null) { + throw new IOException("Parameters not initialized"); + } + + try { + /* Convert BigIntegers to byte arrays */ + byte[] pBytes = this.p.toByteArray(); + byte[] gBytes = this.g.toByteArray(); + + /* Encode as ASN.1 SEQUENCE { prime, generator } */ + ByteArrayOutputStream seq = new ByteArrayOutputStream(); + + /* Encode p as INTEGER */ + seq.write(0x02); /* INTEGER tag */ + seq.write(WolfCryptASN1Util.encodeDERLength(pBytes.length)); + seq.write(pBytes); + + /* Encode g as INTEGER */ + seq.write(0x02); /* INTEGER tag */ + seq.write(WolfCryptASN1Util.encodeDERLength(gBytes.length)); + seq.write(gBytes); + + byte[] seqBytes = seq.toByteArray(); + + /* Wrap in SEQUENCE */ + ByteArrayOutputStream result = new ByteArrayOutputStream(); + result.write(0x30); /* SEQUENCE tag */ + result.write(WolfCryptASN1Util.encodeDERLength(seqBytes.length)); + result.write(seqBytes); + + return result.toByteArray(); + + } catch (Exception e) { + throw new IOException( + "Failed to encode DH parameters: " + e.getMessage()); + } + } + + @Override + protected String engineToString() { + if (this.p == null || this.g == null) { + return "DH Parameters: not initialized"; + } + + return "DH Parameters:\n" + + " p: " + this.p.toString(16) + "\n" + + " g: " + this.g.toString(16) + + (this.l > 0 ? "\n l: " + this.l : ""); + } +} + + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java index d8627ff8..14ede570 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java @@ -235,6 +235,7 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset) throws IllegalStateException, ShortBufferException { byte tmp[] = null; + int returnLen = 0; if (this.state != EngineState.WC_PUBKEY_DONE) throw new IllegalStateException( @@ -248,12 +249,6 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset) switch (this.type) { case WC_DH: - if ((sharedSecret.length - offset) < this.primeLen) { - throw new ShortBufferException( - "Input buffer too small when generating " + - "shared secret"); - } - /* public key has been stored inside this.dh already */ tmp = this.dh.makeSharedSecret(); if (tmp == null) { @@ -261,19 +256,49 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset) "shared secret"); } - if ((sharedSecret.length - offset) < tmp.length) { + /* DH shared secrets can vary in length depending on if they + * are padded or not at the beginning with zero bytes to make + * a total output size matching the prime length. + * + * Native wolfCrypt does not prepend zero bytes to DH shared + * secrets, following RFC 5246 (8.1.2) which instructs to + * strip leading zero bytes. + * + * Sun KeyAgreement DH implementations as of after Java 8 + * prepend zero bytes if total length is not equal to prime + * length. This was changed with OpenJDK bug fix JDK-7146728. + * + * BouncyCastle also behaves the same way, prepending zero + * bytes if total secret size is not prime length. This + * follows RFC 2631 (2.1.2). + * + * To match Sun and BC behavior, we pad the secret to primeLen + * by prepending zeros for both generateSecret() methods. + */ + byte[] paddedSecret = new byte[this.primeLen]; + Arrays.fill(paddedSecret, (byte)0); + System.arraycopy(tmp, 0, paddedSecret, + paddedSecret.length - tmp.length, tmp.length); + + if ((sharedSecret.length - offset) < paddedSecret.length) { zeroArray(tmp); + zeroArray(paddedSecret); throw new ShortBufferException( "Output buffer too small when generating " + "DH shared secret"); } - /* copy array back to output offset */ - System.arraycopy(tmp, 0, sharedSecret, offset, tmp.length); + /* copy padded array back to output offset */ + System.arraycopy(paddedSecret, 0, sharedSecret, offset, + paddedSecret.length); + + returnLen = this.primeLen; /* reset state, using same private info and alg params */ this.state = EngineState.WC_PRIVKEY_DONE; + zeroArray(paddedSecret); + break; case WC_ECDH: @@ -294,6 +319,8 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset) /* copy array back to output ofset */ System.arraycopy(tmp, 0, sharedSecret, offset, tmp.length); + returnLen = tmp.length; + /* reset state, using same private info and alg params */ byte[] priv = this.ecPrivate.exportPrivate(); if (priv == null) { @@ -313,15 +340,11 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset) break; }; - if (tmp != null) { - - log("generated secret, len: " + tmp.length); + log("generated secret, len: " + returnLen); - zeroArray(tmp); - return tmp.length; - } + zeroArray(tmp); - return 0; + return returnLen; } @Override @@ -330,19 +353,66 @@ protected SecretKey engineGenerateSecret(String algorithm) InvalidKeyException { byte secret[] = engineGenerateSecret(); + byte[] keyMaterial = null; + SecretKey ret = null; log("generating SecretKey for " + algorithm); - if (algorithm.equals("DES")) { - return (SecretKey)new DESKeySpec(secret); + try { + if (algorithm.equals("DES")) { + /* DES requires 8 bytes */ + if (secret.length < DESKeySpec.DES_KEY_LEN) { + throw new InvalidKeyException( + "Shared secret is too short for DES key"); + } + keyMaterial = new byte[DESKeySpec.DES_KEY_LEN]; + System.arraycopy(secret, 0, keyMaterial, 0, + DESKeySpec.DES_KEY_LEN); + ret = new SecretKeySpec(keyMaterial, algorithm); + + } else if (algorithm.equals("DESede")) { + /* DESede requires 24 bytes (3-key) */ + if (secret.length < DESedeKeySpec.DES_EDE_KEY_LEN) { + throw new InvalidKeyException( + "Shared secret is too short for DESede key"); + } + keyMaterial = new byte[DESedeKeySpec.DES_EDE_KEY_LEN]; + System.arraycopy(secret, 0, keyMaterial, 0, + DESedeKeySpec.DES_EDE_KEY_LEN); + ret = new SecretKeySpec(keyMaterial, algorithm); + + } else if (algorithm.equals("AES")) { + /* AES requires specific key sizes: 128, 192, or 256 bits. + * Use first 16 bytes (128-bit) by default, or 32 bytes + * (256-bit) if shared secret is >= 32 bytes. */ + int aesKeyLen = 16; /* default to AES-128 */ + if (secret.length >= 32) { + aesKeyLen = 32; /* use AES-256 if possible */ + } else if (secret.length >= 24) { + aesKeyLen = 24; /* use AES-192 if >= 24 bytes */ + } + + if (secret.length < aesKeyLen) { + throw new InvalidKeyException( + "Shared secret is too short for AES key " + + "(need at least " + aesKeyLen + " bytes)"); + } + + keyMaterial = new byte[aesKeyLen]; + System.arraycopy(secret, 0, keyMaterial, 0, aesKeyLen); + ret = new SecretKeySpec(keyMaterial, algorithm); - } else if (algorithm.equals("DESede")) { - return (SecretKey)new DESedeKeySpec(secret); + } else { + /* Other algorithms: use full shared secret */ + ret = new SecretKeySpec(secret, algorithm); + } - } else { - /* AES and default */ - return new SecretKeySpec(secret, algorithm); + } finally { + zeroArray(secret); + zeroArray(keyMaterial); } + + return ret; } /** diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java index 82e8d39e..541583c4 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java @@ -28,6 +28,7 @@ import java.security.InvalidAlgorithmParameterException; import java.security.SecureRandom; +import java.security.InvalidParameterException; import java.security.spec.AlgorithmParameterSpec; import java.security.KeyFactory; import java.security.spec.KeySpec; @@ -114,6 +115,42 @@ private WolfCryptKeyPairGenerator(KeyType type) { } } + /* Set default parameters for DH key generation. + * Try FFDHE 3072 first (matches SunJCE default), but fall back + * to FFDHE 2048 if 3072 is not available in the wolfSSL build. */ + if (type == KeyType.WC_DH) { + try { + /* Try FFDHE 3072 first */ + byte[][] params = Dh.getNamedDhParams(Dh.WC_FFDHE_3072); + if (params != null && params.length == 2 && + params[0] != null && params[0].length > 0) { + this.dhP = params[0]; + this.dhG = params[1]; + } + else { + /* Fall back to FFDHE 2048 if 3072 not available */ + params = Dh.getNamedDhParams(Dh.WC_FFDHE_2048); + if (params != null && params.length == 2 && + params[0] != null && params[0].length > 0) { + this.dhP = params[0]; + this.dhG = params[1]; + } + } + } + catch (Exception e) { + /* Not fatal if default param initialization. User can still + * initialize() before generateKeyPair() */ + } + + /* Initialize RNG for default key generation */ + synchronized (rngLock) { + if (this.rng == null) { + this.rng = new Rng(); + this.rng.init(); + } + } + } + if (WolfCryptDebug.DEBUG) { algString = typeToString(type); } @@ -123,9 +160,68 @@ private WolfCryptKeyPairGenerator(KeyType type) { public synchronized void initialize(int keysize, SecureRandom random) { if (type == KeyType.WC_DH) { - throw new RuntimeException( - "wolfJCE requires users to explicitly set DH parameters, " + - "please call initialize() with DHParameterSpec"); + int namedGroup = -1; + + /* Map key size (in bits) to FFDHE named group. + * Only standard FFDHE sizes are supported (2048, 3072, 4096, + * 6144, 8192). Throw InvalidParameterException for unsupported + * sizes, matching SunJCE behavior. */ + if (keysize == 2048) { + namedGroup = Dh.WC_FFDHE_2048; + } + else if (keysize == 3072) { + namedGroup = Dh.WC_FFDHE_3072; + } + else if (keysize == 4096) { + namedGroup = Dh.WC_FFDHE_4096; + } + else if (keysize == 6144) { + namedGroup = Dh.WC_FFDHE_6144; + } + else if (keysize == 8192) { + namedGroup = Dh.WC_FFDHE_8192; + } + else { + throw new java.security.InvalidParameterException( + "DH key size must be 2048, 3072, 4096, 6144, or 8192. " + + "Unsupported size: " + keysize); + } + + try { + /* Get DH parameters for named group. If this FFDHE group + * is not compiled into wolfSSL, throw an exception. */ + byte[][] params = Dh.getNamedDhParams(namedGroup); + if (params != null && params.length == 2 && + params[0] != null && params[0].length > 0) { + this.dhP = params[0]; + this.dhG = params[1]; + } + else { + throw new java.security.InvalidParameterException( + "FFDHE " + keysize + "-bit group not available in " + + "native wolfSSL library. Only FFDHE groups compiled " + + "into wolfSSL can be used."); + } + } + catch (InvalidParameterException e) { + throw e; + } + catch (Exception e) { + throw new RuntimeException( + "Failed to initialize DH parameters: " + e.getMessage()); + } + + synchronized (rngLock) { + if (this.rng == null) { + this.rng = new Rng(); + this.rng.init(); + } + } + + log("init with DH keysize: " + keysize + + " (using FFDHE group " + namedGroup + ")"); + + return; } if (type == KeyType.WC_ECC) { @@ -408,9 +504,7 @@ public synchronized KeyPair generateKeyPair() { DHPublicKey dhPub = null; if (dhP == null || dhG == null) { - throw new RuntimeException( - "No DH parameters set, wolfJCE requires users to " + - "set through KeyPairGenerator.initialize()"); + throw new RuntimeException("No DH parameters available"); } Dh dh = new Dh(); @@ -424,14 +518,14 @@ public synchronized KeyPair generateKeyPair() { } privSpec = new DHPrivateKeySpec( - new BigInteger(dh.getPrivateKey()), - new BigInteger(dhP), - new BigInteger(dhG)); + new BigInteger(1, dh.getPrivateKey()), + new BigInteger(1, dhP), + new BigInteger(1, dhG)); pubSpec = new DHPublicKeySpec( - new BigInteger(dh.getPublicKey()), - new BigInteger(dhP), - new BigInteger(dhG)); + new BigInteger(1, dh.getPublicKey()), + new BigInteger(1, dhP), + new BigInteger(1, dhG)); dh.releaseNativeStruct(); diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java index 6b0c4748..5df6bca1 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java @@ -355,6 +355,16 @@ private void registerServices() { put("KeyAgreement.DiffieHellman", "com.wolfssl.provider.jce.WolfCryptKeyAgreement$wcDH"); put("Alg.Alias.KeyAgreement.DH", "DiffieHellman"); + + /* DH AlgorithmParameters */ + put("AlgorithmParameters.DH", + "com.wolfssl.provider.jce.WolfCryptDhParameters"); + put("Alg.Alias.AlgorithmParameters.DiffieHellman", "DH"); + + /* DH AlgorithmParameterGenerator */ + put("AlgorithmParameterGenerator.DH", + "com.wolfssl.provider.jce.WolfCryptDhParameterGenerator"); + put("Alg.Alias.AlgorithmParameterGenerator.DiffieHellman", "DH"); } if (FeatureDetect.EccDheEnabled()) { put("KeyAgreement.ECDH", @@ -476,6 +486,12 @@ private void registerServices() { put("KeyFactory.EC", "com.wolfssl.provider.jce.WolfCryptECKeyFactory"); } + if (FeatureDetect.DhEnabled()) { + put("KeyFactory.DH", + "com.wolfssl.provider.jce.WolfCryptDHKeyFactory"); + put("Alg.Alias.KeyFactory.DiffieHellman", "DH"); + put("Alg.Alias.KeyFactory.1.2.840.113549.1.3.1", "DH"); + } /* KeyStore */ put("KeyStore.WKS", diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java index b24fe935..15da41a0 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java @@ -42,6 +42,7 @@ import java.security.spec.AlgorithmParameterSpec; import java.security.spec.PSSParameterSpec; import java.security.spec.MGF1ParameterSpec; +import java.security.interfaces.ECKey; import javax.crypto.ShortBufferException; @@ -326,13 +327,58 @@ private void wolfCryptInitPrivateKey(PrivateKey key, byte[] encodedKey) case WC_ECDSA: + int curveSize; + byte[] privKeyBytes; ECPrivateKey ecPriv = (ECPrivateKey)key; - this.ecc.importPrivate(ecPriv.getS().toByteArray(), null); + + try { + /* For ECC private keys, get curve size and ensure + * private key bytes are properly sized */ + curveSize = getCurveSizeFromKey(ecPriv); + + /* Convert BigInteger to byte array with proper padding */ + privKeyBytes = Ecc.bigIntToFixedSizeByteArray( + ecPriv.getS(), curveSize); + + } catch (Exception e) { + /* Fallback to original behavior on failure */ + privKeyBytes = ecPriv.getS().toByteArray(); + } + + this.ecc.importPrivate(privKeyBytes, null); break; } } + /** + * Get curve size in bytes from an EC key. + * + * @param ecKey EC private or public key + * + * @return curve size in bytes + * + * @throws WolfCryptException if curve size cannot be determined + * @throws InvalidAlgorithmParameterException if curve parameters + * are invalid + */ + private int getCurveSizeFromKey(ECKey ecKey) + throws WolfCryptException, InvalidAlgorithmParameterException { + + int curveSize; + String curveName; + + curveName = Ecc.getCurveName(ecKey.getParams()); + curveSize = Ecc.getCurveSizeFromName(curveName.toUpperCase()); + + if (curveSize <= 0) { + throw new WolfCryptException( + "Invalid curve size for curve: " + curveName); + } + + return curveSize; + } + private void wolfCryptInitPublicKey(PublicKey key, byte[] encodedKey) throws InvalidKeyException { @@ -1719,7 +1765,6 @@ public wcSHA512wRSAPSS() throws NoSuchAlgorithmException { /** * Get the component size in bytes for P1363 format based on curve. - * This is calculated as ceil(curve_bits / 8). * * @param ecc ECC key to get curve size from * @@ -1730,11 +1775,27 @@ public wcSHA512wRSAPSS() throws NoSuchAlgorithmException { private static int getP1363ComponentSize(Ecc ecc) throws SignatureException { + int curveId; + String curveName; int componentSize; try { - /* Get curve size in bytes, gives us the component size */ - componentSize = ecc.getCurveSizeByKey(); + /* Get curve ID from key */ + curveId = ecc.getCurveId(); + if (curveId < 0) { + throw new SignatureException( + "Invalid curve ID for P1363 format"); + } + + /* Get curve name from ID */ + curveName = Ecc.getCurveNameFromId(curveId); + if (curveName == null) { + throw new SignatureException( + "Failed to get curve name for P1363 format"); + } + + /* Get fixed curve field size from name */ + componentSize = Ecc.getCurveSizeFromName(curveName.toUpperCase()); if (componentSize <= 0) { throw new SignatureException( "Invalid curve size for P1363 format"); diff --git a/src/main/java/com/wolfssl/wolfcrypt/Dh.java b/src/main/java/com/wolfssl/wolfcrypt/Dh.java index a33d156a..44e2b4ce 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Dh.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Dh.java @@ -38,6 +38,18 @@ public class Dh extends NativeStruct { /** Lock around object state */ protected final Object stateLock = new Object(); + /* Named DH group constants (FFDHE from RFC 7919) */ + /** FFDHE 2048-bit group */ + public static final int WC_FFDHE_2048 = 256; + /** FFDHE 3072-bit group */ + public static final int WC_FFDHE_3072 = 257; + /** FFDHE 4096-bit group */ + public static final int WC_FFDHE_4096 = 258; + /** FFDHE 6144-bit group */ + public static final int WC_FFDHE_6144 = 259; + /** FFDHE 8192-bit group */ + public static final int WC_FFDHE_8192 = 260; + /** * Create new Dh object. * @@ -97,6 +109,17 @@ public synchronized void releaseNativeStruct() { private native void wc_DhSetKey(byte[] p, byte[] g); private native void wc_DhGenerateKeyPair(Rng rng, int pSize); private native byte[] wc_DhAgree(byte[] priv, byte[] pub); + private native void wc_DhCheckPubKey(byte[] pub); + private static native byte[][] wc_DhCopyNamedKey(int name); + private static native byte[][] wc_DhGenerateParams(Rng rng, int modSz); + private native void wc_DhImportKeyPair(byte[] priv, byte[] pub, + byte[] p, byte[] g); + private native byte[][] wc_DhExportKeyPair(); + private native byte[][] wc_DhExportParams(); + private native byte[] wc_DhPrivateKeyDecode(byte[] pkcs8); + private native byte[] wc_DhPrivateKeyEncode(); + private native byte[] wc_DhPublicKeyDecode(byte[] x509); + private native byte[] wc_DhPublicKeyEncode(); /** * Malloc native JNI DH structure @@ -353,5 +376,242 @@ public synchronized byte[] makeSharedSecret(byte[] pubKey) return wc_DhAgree(this.privateKey, pubKey); } } + + /** + * Get named DH parameters (FFDHE groups from RFC 7919). + * + * Returns an array containing [p, g] parameters for the named group. + * + * @param name Named DH group constant (WC_FFDHE_2048, WC_FFDHE_3072, + * WC_FFDHE_4096, WC_FFDHE_6144, or WC_FFDHE_8192) + * + * @return byte array containing [p, g] parameters, or null on error + * + * @throws WolfCryptException if native operation fails or named group + * is not supported + */ + public static byte[][] getNamedDhParams(int name) + throws WolfCryptException { + + if (!FeatureDetect.DhEnabled()) { + throw new WolfCryptException( + WolfCryptError.NOT_COMPILED_IN.getCode()); + } + + return wc_DhCopyNamedKey(name); + } + + /** + * Generate DH parameters dynamically. + * + * Returns an array containing [p, g] parameters for the specified + * modulus size. + * + * This method generates DH parameters at runtime, which can be slow + * for larger modulus sizes. For standard sizes (2048, 3072, 4096, + * 6144, 8192), consider using getNamedDhParams() which uses + * pre-computed FFDHE parameters from RFC 7919. + * + * @param rng Initialized Rng object to use for parameter generation + * @param modSz Modulus size in bits (e.g., 512, 1024, 2048) + * + * @return byte array containing [p, g] parameters, or null on error + * + * @throws WolfCryptException if native operation fails or if + * parameter generation is not supported + */ + public static byte[][] generateDhParams(Rng rng, int modSz) + throws WolfCryptException { + + if (!FeatureDetect.DhEnabled()) { + throw new WolfCryptException( + WolfCryptError.NOT_COMPILED_IN.getCode()); + } + + if (rng == null) { + throw new WolfCryptException("Rng cannot be null"); + } + + if (modSz <= 0) { + throw new WolfCryptException("Invalid modulus size: " + modSz); + } + + return wc_DhGenerateParams(rng, modSz); + } + + /** + * Import DH key pair with parameters into this Dh object. + * + * @param priv Private key value as byte array + * @param pub Public key value as byte array + * @param p Prime modulus parameter + * @param g Base generator parameter + * + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if object fails to initialize, or if + * releaseNativeStruct() has been called and object has been + * released + */ + public synchronized void importKeyPair(byte[] priv, byte[] pub, + byte[] p, byte[] g) + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + + synchronized (pointerLock) { + wc_DhImportKeyPair(priv, pub, p, g); + } + + /* Reset stored keys if new ones are imported */ + this.privateKey = null; + this.publicKey = null; + this.pSize = 0; + + if (priv != null) { + this.privateKey = priv.clone(); + } + + if (pub != null) { + this.publicKey = pub.clone(); + } + + if (p != null) { + this.pSize = p.length; + } + + state = WolfCryptState.READY; + } + + /** + * Export DH key pair from this Dh object. + * + * @return byte[][] array containing [privateKey, publicKey] + * + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if object fails to initialize, or if + * releaseNativeStruct() has been called and object has been + * released + */ + public synchronized byte[][] exportKeyPair() + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + + synchronized (pointerLock) { + return wc_DhExportKeyPair(); + } + } + + /** + * Export DH parameters from this Dh object. + * + * @return byte[][] array containing [p, g, q] or [p, g] if q is null + * + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if object fails to initialize, or if + * releaseNativeStruct() has been called and object has been + * released + */ + public synchronized byte[][] exportParams() + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + + synchronized (pointerLock) { + return wc_DhExportParams(); + } + } + + /** + * Decode and import DH private key from PKCS#8 DER format. + * + * @param pkcs8 DER-encoded PKCS#8 private key + * + * @return DER-encoded private key (may be re-encoded by wolfCrypt) + * + * @throws WolfCryptException if native operation fails or DER is invalid + * @throws IllegalStateException if object fails to initialize, or if + * releaseNativeStruct() has been called and object has been + * released + */ + public synchronized byte[] privateKeyDecodePKCS8(byte[] pkcs8) + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + + if (pkcs8 == null || pkcs8.length == 0) { + throw new WolfCryptException("PKCS#8 data cannot be null or empty"); + } + + synchronized (pointerLock) { + return wc_DhPrivateKeyDecode(pkcs8); + } + } + + /** + * Encode and export DH private key to PKCS#8 DER format. + * + * @return DER-encoded PKCS#8 private key + * + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if object fails to initialize, or if + * releaseNativeStruct() has been called and object has been + * released + */ + public synchronized byte[] privateKeyEncodePKCS8() + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + + synchronized (pointerLock) { + return wc_DhPrivateKeyEncode(); + } + } + + /** + * Decode and import DH public key from X.509 DER format. + * + * @param x509 DER-encoded X.509 public key + * + * @return DER-encoded public key (may be re-encoded by wolfCrypt) + * + * @throws WolfCryptException if native operation fails or DER is invalid + * @throws IllegalStateException if object fails to initialize, or if + * releaseNativeStruct() has been called and object has been + * released + */ + public synchronized byte[] publicKeyDecodeX509(byte[] x509) + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + + if (x509 == null || x509.length == 0) { + throw new WolfCryptException("X.509 data cannot be null or empty"); + } + + synchronized (pointerLock) { + return wc_DhPublicKeyDecode(x509); + } + } + + /** + * Encode and export DH public key to X.509 DER format. + * + * @return DER-encoded X.509 public key + * + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if object fails to initialize, or if + * releaseNativeStruct() has been called and object has been + * released + */ + public synchronized byte[] publicKeyEncodeX509() + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + + synchronized (pointerLock) { + return wc_DhPublicKeyEncode(); + } + } } diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptASN1UtilTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptASN1UtilTest.java new file mode 100644 index 00000000..aad09462 --- /dev/null +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptASN1UtilTest.java @@ -0,0 +1,545 @@ +/* WolfCryptASN1UtilTest.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.Test; +import org.junit.BeforeClass; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +import java.math.BigInteger; + +import com.wolfssl.provider.jce.WolfCryptASN1Util; + +/** + * JUnit test class for WolfCryptASN1Util DER encoding utilities. + */ +public class WolfCryptASN1UtilTest { + + @Rule(order = Integer.MIN_VALUE) + public TestRule testWatcher = new TestWatcher() { + protected void starting(Description desc) { + System.out.println("\t" + desc.getMethodName()); + } + }; + + /* ASN.1 Universal Tags */ + private static final byte ASN1_INTEGER = 0x02; + private static final byte ASN1_BIT_STRING = 0x03; + private static final byte ASN1_OCTET_STRING = 0x04; + private static final byte ASN1_OBJECT_IDENTIFIER = 0x06; + private static final byte ASN1_SEQUENCE = 0x30; + + @BeforeClass + public static void testEntry() { + System.out.println("JCE WolfCryptASN1UtilTest Class"); + } + + @Test + public void testEncodeDERLengthShortForm() throws Exception { + /* Short form: length < 128 */ + byte[] result = WolfCryptASN1Util.encodeDERLength(0); + assertArrayEquals(new byte[] { 0x00 }, result); + + result = WolfCryptASN1Util.encodeDERLength(1); + assertArrayEquals(new byte[] { 0x01 }, result); + + result = WolfCryptASN1Util.encodeDERLength(127); + assertArrayEquals(new byte[] { 0x7F }, result); + } + + @Test + public void testEncodeDERLengthLongFormOneByte() throws Exception { + /* Long form: length = 128 (requires 1 length byte) */ + byte[] result = WolfCryptASN1Util.encodeDERLength(128); + assertArrayEquals(new byte[] { (byte)0x81, (byte)0x80 }, result); + + result = WolfCryptASN1Util.encodeDERLength(255); + assertArrayEquals(new byte[] { (byte)0x81, (byte)0xFF }, result); + } + + @Test + public void testEncodeDERLengthLongFormTwoBytes() throws Exception { + /* Long form: length = 256 (requires 2 length bytes) */ + byte[] result = WolfCryptASN1Util.encodeDERLength(256); + assertArrayEquals(new byte[] { (byte)0x82, 0x01, 0x00 }, result); + + result = WolfCryptASN1Util.encodeDERLength(1000); + assertArrayEquals(new byte[] { (byte)0x82, 0x03, (byte)0xE8 }, result); + + result = WolfCryptASN1Util.encodeDERLength(65535); + assertArrayEquals( + new byte[] { (byte)0x82, (byte)0xFF, (byte)0xFF }, result); + } + + @Test + public void testEncodeDERLengthLongFormThreeBytes() throws Exception { + /* Long form: length = 65536 (requires 3 length bytes) */ + byte[] result = WolfCryptASN1Util.encodeDERLength(65536); + assertArrayEquals( + new byte[] { (byte)0x83, 0x01, 0x00, 0x00 }, result); + } + + @Test + public void testEncodeDERLengthLongFormFourBytes() throws Exception { + /* Long form: length = 16777216 (requires 4 length bytes) */ + byte[] result = WolfCryptASN1Util.encodeDERLength(16777216); + assertArrayEquals( + new byte[] { (byte)0x84, 0x01, 0x00, 0x00, 0x00 }, result); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeDERLengthNegativeThrows() throws Exception { + WolfCryptASN1Util.encodeDERLength(-1); + } + + @Test + public void testEncodeDERIntegerZero() throws Exception { + byte[] result = WolfCryptASN1Util.encodeDERInteger(BigInteger.ZERO); + /* INTEGER tag + length 1 + value 0x00 */ + assertArrayEquals(new byte[] { 0x02, 0x01, 0x00 }, result); + } + + @Test + public void testEncodeDERIntegerOne() throws Exception { + byte[] result = WolfCryptASN1Util.encodeDERInteger(BigInteger.ONE); + /* INTEGER tag + length 1 + value 0x01 */ + assertArrayEquals(new byte[] { 0x02, 0x01, 0x01 }, result); + } + + @Test + public void testEncodeDERIntegerSmallPositive() throws Exception { + byte[] result = WolfCryptASN1Util.encodeDERInteger( + BigInteger.valueOf(127)); + /* INTEGER tag + length 1 + value 0x7F */ + assertArrayEquals(new byte[] { 0x02, 0x01, 0x7F }, result); + } + + @Test + public void testEncodeDERIntegerWithSignBit() throws Exception { + /* 128 = 0x80, MSB set, needs leading 0x00 */ + byte[] result = WolfCryptASN1Util.encodeDERInteger( + BigInteger.valueOf(128)); + /* INTEGER tag + length 2 + 0x00 + 0x80 */ + assertArrayEquals(new byte[] { 0x02, 0x02, 0x00, (byte)0x80 }, result); + } + + @Test + public void testEncodeDERIntegerLargeValue() throws Exception { + /* 256-bit value */ + BigInteger large = new BigInteger( + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFF", 16); + byte[] result = WolfCryptASN1Util.encodeDERInteger(large); + + /* Verify tag and structure */ + assertEquals(ASN1_INTEGER, result[0]); + /* Length should be 33 (32 bytes + leading 0x00 for sign) */ + assertEquals(0x21, result[1]); + /* First value byte should be 0x00 (sign bit) */ + assertEquals(0x00, result[2]); + } + + @Test + public void testEncodeDERIntegerTwoBytes() throws Exception { + byte[] result = WolfCryptASN1Util.encodeDERInteger( + BigInteger.valueOf(255)); + /* INTEGER tag + length 2 + 0x00 + 0xFF */ + assertArrayEquals(new byte[] { 0x02, 0x02, 0x00, (byte)0xFF }, result); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeDERIntegerNullThrows() throws Exception { + WolfCryptASN1Util.encodeDERInteger(null); + } + + @Test + public void testEncodeDERSequenceEmpty() throws Exception { + byte[] result = WolfCryptASN1Util.encodeDERSequence(new byte[0]); + /* SEQUENCE tag + length 0 */ + assertArrayEquals(new byte[] { 0x30, 0x00 }, result); + } + + @Test + public void testEncodeDERSequenceSimple() throws Exception { + byte[] contents = new byte[] { 0x01, 0x02, 0x03 }; + byte[] result = WolfCryptASN1Util.encodeDERSequence(contents); + /* SEQUENCE tag + length 3 + contents */ + assertArrayEquals( + new byte[] { 0x30, 0x03, 0x01, 0x02, 0x03 }, result); + } + + @Test + public void testEncodeDERSequenceLongLength() throws Exception { + /* Create contents requiring long form length (> 127 bytes) */ + byte[] contents = new byte[200]; + for (int i = 0; i < contents.length; i++) { + contents[i] = (byte) i; + } + + byte[] result = WolfCryptASN1Util.encodeDERSequence(contents); + + /* SEQUENCE tag */ + assertEquals(ASN1_SEQUENCE, result[0]); + /* Long form length: 0x81 (1 byte follows) + 0xC8 (200) */ + assertEquals((byte)0x81, result[1]); + assertEquals((byte)0xC8, result[2]); + /* First content byte */ + assertEquals(0x00, result[3]); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeDERSequenceNullThrows() throws Exception { + WolfCryptASN1Util.encodeDERSequence(null); + } + + @Test + public void testEncodeDEROctetStringEmpty() throws Exception { + byte[] result = WolfCryptASN1Util.encodeDEROctetString(new byte[0]); + /* OCTET STRING tag + length 0 */ + assertArrayEquals(new byte[] { 0x04, 0x00 }, result); + } + + @Test + public void testEncodeDEROctetStringSimple() throws Exception { + byte[] contents = new byte[] { 0x01, 0x02, 0x03 }; + byte[] result = WolfCryptASN1Util.encodeDEROctetString(contents); + /* OCTET STRING tag + length 3 + contents */ + assertArrayEquals( + new byte[] { 0x04, 0x03, 0x01, 0x02, 0x03 }, result); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeDEROctetStringNullThrows() throws Exception { + WolfCryptASN1Util.encodeDEROctetString(null); + } + + @Test + public void testEncodeDERBitStringEmpty() throws Exception { + byte[] result = WolfCryptASN1Util.encodeDERBitString(new byte[0]); + /* BIT STRING tag + length 1 (includes unused bits) + 0x00 */ + assertArrayEquals(new byte[] { 0x03, 0x01, 0x00 }, result); + } + + @Test + public void testEncodeDERBitStringSimple() throws Exception { + byte[] contents = new byte[] { 0x01, 0x02, 0x03 }; + byte[] result = WolfCryptASN1Util.encodeDERBitString(contents); + /* BIT STRING tag + length 4 + unused bits 0x00 + contents */ + assertArrayEquals( + new byte[] { 0x03, 0x04, 0x00, 0x01, 0x02, 0x03 }, result); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeDERBitStringNullThrows() throws Exception { + WolfCryptASN1Util.encodeDERBitString(null); + } + + @Test + public void testGetDHAlgorithmOID() throws Exception { + byte[] oid = WolfCryptASN1Util.getDHAlgorithmOID(); + + /* DH OID: 1.2.840.113549.1.3.1 (PKCS #3) */ + /* Expected: 0x06 0x09 0x2A 0x86 0x48 0x86 0xF7 0x0D 0x01 0x03 0x01 */ + assertNotNull(oid); + assertEquals(11, oid.length); + assertEquals(ASN1_OBJECT_IDENTIFIER, oid[0]); + assertEquals(0x09, oid[1]); /* Length */ + assertEquals(0x2A, oid[2]); /* First arc: 1.2 = 40*1+2 = 42 = 0x2A */ + } + + @Test + public void testEncodeDHParametersSimple() throws Exception { + BigInteger p = BigInteger.valueOf(23); + BigInteger g = BigInteger.valueOf(5); + + byte[] result = WolfCryptASN1Util.encodeDHParameters(p, g); + + /* Should be SEQUENCE containing two INTEGERs */ + assertEquals(ASN1_SEQUENCE, result[0]); + /* Content starts at index 2 (tag + length) */ + assertEquals(ASN1_INTEGER, result[2]); /* p */ + assertEquals(ASN1_INTEGER, result[5]); /* g */ + } + + @Test + public void testEncodeDHParametersLarge() throws Exception { + /* Use realistic 2048-bit DH parameters */ + BigInteger p = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16); + BigInteger g = BigInteger.valueOf(2); + + byte[] result = WolfCryptASN1Util.encodeDHParameters(p, g); + + /* Verify structure */ + assertEquals(ASN1_SEQUENCE, result[0]); + assertTrue("Result should be substantial", result.length > 256); + + /* For large parameters, length is long-form encoded */ + /* result[0] = SEQUENCE tag (0x30) */ + /* result[1] = 0x82 (long form, 2 bytes follow) */ + /* result[2-3] = length bytes */ + /* result[4] = first content byte (should be INTEGER tag) */ + int idx; + if ((result[1] & 0x80) != 0) { + /* Long form length */ + int numLengthBytes = result[1] & 0x7F; + /* Skip tag, length indicator, and length bytes */ + idx = 2 + numLengthBytes; + } else { + /* Short form length */ + idx = 2; + } + assertEquals("First content should be INTEGER tag", + ASN1_INTEGER, result[idx]); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeDHParametersNullPThrows() throws Exception { + WolfCryptASN1Util.encodeDHParameters(null, BigInteger.valueOf(2)); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeDHParametersNullGThrows() throws Exception { + WolfCryptASN1Util.encodeDHParameters(BigInteger.valueOf(23), null); + } + + @Test + public void testEncodeDHAlgorithmIdentifier() throws Exception { + BigInteger p = BigInteger.valueOf(23); + BigInteger g = BigInteger.valueOf(5); + + byte[] result = WolfCryptASN1Util.encodeDHAlgorithmIdentifier(p, g); + + /* Should be SEQUENCE containing OID and parameters SEQUENCE */ + assertEquals(ASN1_SEQUENCE, result[0]); + + /* Find OID tag */ + int idx = 2; /* Skip outer SEQUENCE tag and length */ + assertEquals(ASN1_OBJECT_IDENTIFIER, result[idx]); + + /* OID length should be 9 */ + assertEquals(0x09, result[idx + 1]); + + /* After OID (11 bytes total: tag + length + 9 bytes) should be + * parameters SEQUENCE */ + idx += 11; + assertEquals(ASN1_SEQUENCE, result[idx]); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeDHAlgorithmIdentifierNullPThrows() throws Exception { + WolfCryptASN1Util.encodeDHAlgorithmIdentifier( + null, BigInteger.valueOf(2)); + } + + @Test(expected = IllegalArgumentException.class) + public void testEncodeDHAlgorithmIdentifierNullGThrows() throws Exception { + WolfCryptASN1Util.encodeDHAlgorithmIdentifier( + BigInteger.valueOf(23), null); + } + + @Test + public void testEncodeDERIntegerMaxByte() throws Exception { + /* Test boundary: max signed byte value (127) */ + byte[] result = WolfCryptASN1Util.encodeDERInteger( + BigInteger.valueOf(127)); + assertArrayEquals(new byte[] { 0x02, 0x01, 0x7F }, result); + } + + @Test + public void testEncodeDERIntegerMinUnsignedByte() throws Exception { + /* Test boundary: min value requiring 2 bytes (128) */ + byte[] result = WolfCryptASN1Util.encodeDERInteger( + BigInteger.valueOf(128)); + assertArrayEquals(new byte[] { 0x02, 0x02, 0x00, (byte)0x80 }, result); + } + + @Test + public void testEncodeDERIntegerMaxShort() throws Exception { + /* Test boundary: max signed short value (32767) */ + byte[] result = WolfCryptASN1Util.encodeDERInteger( + BigInteger.valueOf(32767)); + assertArrayEquals(new byte[] { 0x02, 0x02, 0x7F, (byte)0xFF }, result); + } + + @Test + public void testEncodeDERIntegerMinUnsignedShort() throws Exception { + /* Test boundary: 32768 requires leading zero */ + byte[] result = WolfCryptASN1Util.encodeDERInteger( + BigInteger.valueOf(32768)); + assertArrayEquals( + new byte[] { 0x02, 0x03, 0x00, (byte)0x80, 0x00 }, result); + } + + @Test + public void testEncodeDERSequenceNested() throws Exception { + /* Create nested SEQUENCE structure */ + byte[] inner = + WolfCryptASN1Util.encodeDERSequence(new byte[] { 0x01, 0x02 }); + byte[] outer = WolfCryptASN1Util.encodeDERSequence(inner); + + /* Verify outer structure */ + assertEquals(ASN1_SEQUENCE, outer[0]); + /* Verify inner structure is embedded */ + assertEquals(ASN1_SEQUENCE, outer[2]); + } + + @Test + public void testEncodeDERIntegerPowerOfTwo() throws Exception { + /* Test powers of 2 - some need leading zero, some don't */ + /* 2^7 = 128 = 0x80 - MSB set, needs leading zero */ + /* 2^8 = 256 = 0x100 - MSB clear, no leading zero */ + /* 2^15 = 32768 = 0x8000 - MSB set, needs leading zero */ + /* 2^16 = 65536 = 0x10000 - MSB clear, no leading zero */ + + /* Test 2^7 = 128 (needs leading zero) */ + BigInteger val128 = BigInteger.valueOf(128); + byte[] result128 = WolfCryptASN1Util.encodeDERInteger(val128); + assertEquals(ASN1_INTEGER, result128[0]); + /* Length is 2, value is 0x00 0x80 */ + assertArrayEquals(new byte[] { 0x02, 0x02, 0x00, (byte)0x80 }, + result128); + + /* Test 2^8 = 256 (no leading zero needed) */ + BigInteger val256 = BigInteger.valueOf(256); + byte[] result256 = WolfCryptASN1Util.encodeDERInteger(val256); + assertEquals(ASN1_INTEGER, result256[0]); + /* Length is 2, value is 0x01 0x00 (no leading zero) */ + assertArrayEquals(new byte[] { 0x02, 0x02, 0x01, 0x00 }, result256); + } + + @Test + public void testGetDHAlgorithmOIDIsCloned() throws Exception { + /* Verify that calling getDHAlgorithmOID returns independent copies */ + byte[] oid1 = WolfCryptASN1Util.getDHAlgorithmOID(); + byte[] oid2 = WolfCryptASN1Util.getDHAlgorithmOID(); + + /* Should be equal but not same reference */ + assertArrayEquals(oid1, oid2); + assertNotSame("Should return clone, not same reference", oid1, oid2); + + /* Modifying one should not affect the other */ + oid1[0] = 0x00; + assertNotEquals(oid1[0], oid2[0]); + } + + @Test + public void testEncodeDHParametersWithZeroG() throws Exception { + /* Edge case: g = 0 (invalid in practice, but test encoding) */ + BigInteger p = BigInteger.valueOf(23); + BigInteger g = BigInteger.ZERO; + + byte[] result = WolfCryptASN1Util.encodeDHParameters(p, g); + + /* Should encode without error */ + assertEquals(ASN1_SEQUENCE, result[0]); + } + + @Test + public void testEncodeDHParametersWithLargeG() throws Exception { + /* Edge case: g is larger than typical (usually 2 or 5) */ + BigInteger p = BigInteger.valueOf(23); + BigInteger g = BigInteger.valueOf(1000000); + + byte[] result = WolfCryptASN1Util.encodeDHParameters(p, g); + + /* Should encode without error */ + assertEquals(ASN1_SEQUENCE, result[0]); + } + + @Test + public void testEncodeDERLengthBoundaryValues() throws Exception { + /* Test all boundary values for length encoding */ + int[] boundaries = { + 0, 1, 127, /* Short form */ + 128, 255, /* Long form, 1 byte */ + 256, 65535, /* Long form, 2 bytes */ + 65536, 16777215 /* Long form, 3 bytes */ + }; + + for (int length : boundaries) { + byte[] result = WolfCryptASN1Util.encodeDERLength(length); + assertNotNull("Length encoding should succeed for " + length, + result); + assertTrue("Length encoding should produce bytes", + result.length > 0); + } + } + + @Test + public void testEncodeDERIntegerConsistency() throws Exception { + /* Verify that encoding is consistent across calls */ + BigInteger value = new BigInteger( + "123456789ABCDEF0123456789ABCDEF0", 16); + + byte[] result1 = WolfCryptASN1Util.encodeDERInteger(value); + byte[] result2 = WolfCryptASN1Util.encodeDERInteger(value); + + assertArrayEquals("Encoding should be deterministic", + result1, result2); + } + + @Test + public void testEncodeDHAlgorithmIdentifierStructure() throws Exception { + /* Verify complete structure of AlgorithmIdentifier */ + BigInteger p = BigInteger.valueOf(23); + BigInteger g = BigInteger.valueOf(5); + + byte[] result = WolfCryptASN1Util.encodeDHAlgorithmIdentifier(p, g); + + /* Parse and verify structure: + * SEQUENCE { + * OID (0x06 0x09 ...) + * SEQUENCE { + * INTEGER (p) + * INTEGER (g) + * } + * } + */ + int idx = 0; + assertEquals("Should start with SEQUENCE", ASN1_SEQUENCE, result[idx]); + idx += 2; /* Skip tag and length */ + + assertEquals("Should contain OID", ASN1_OBJECT_IDENTIFIER, + result[idx]); + idx += 11; /* OID is 11 bytes total */ + + assertEquals("Should contain parameters SEQUENCE", ASN1_SEQUENCE, + result[idx]); + } +} + diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptAlgorithmParameterGeneratorTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptAlgorithmParameterGeneratorTest.java new file mode 100644 index 00000000..310fe9b6 --- /dev/null +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptAlgorithmParameterGeneratorTest.java @@ -0,0 +1,546 @@ +/* WolfCryptAlgorithmParameterGeneratorTest.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 org.junit.Test; +import org.junit.BeforeClass; + +import java.security.Security; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.AlgorithmParameters; +import java.security.AlgorithmParameterGenerator; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchProviderException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.spec.DHGenParameterSpec; +import javax.crypto.spec.DHParameterSpec; + +import com.wolfssl.wolfcrypt.Fips; +import com.wolfssl.provider.jce.WolfCryptProvider; + +public class WolfCryptAlgorithmParameterGeneratorTest { + + private static SecureRandom rand = null; + + @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 testProviderInstallationAtRuntime() + throws NoSuchAlgorithmException, NoSuchProviderException { + + System.out.println( + "JCE WolfCryptAlgorithmParameterGeneratorTest Class"); + + /* Install wolfJCE provider at runtime */ + Security.insertProviderAt(new WolfCryptProvider(), 1); + + Provider p = Security.getProvider("wolfJCE"); + assertNotNull(p); + + /* Get single static SecureRandom for use in this class */ + rand = SecureRandom.getInstance("DEFAULT"); + } + + @Test + public void testGetAlgorithmParameterGeneratorFromProvider() + throws NoSuchProviderException, NoSuchAlgorithmException { + + AlgorithmParameterGenerator paramGen; + + /* DH should be available */ + paramGen = AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(paramGen); + + /* Getting a garbage algorithm should throw an exception */ + try { + paramGen = AlgorithmParameterGenerator.getInstance( + "NotValid", "wolfJCE"); + + fail("AlgorithmParameterGenerator.getInstance should throw " + + "NoSuchAlgorithmException when given bad algorithm value"); + + } catch (NoSuchAlgorithmException e) { + /* expected */ + } + } + + @Test + public void testDHParameterGenerationFFDHESizes() + throws NoSuchProviderException, NoSuchAlgorithmException, + Exception { + + /* Test standard FFDHE sizes: 2048, 3072, 4096, 6144, 8192 */ + int[] ffdheSizes = { 2048, 3072, 4096, 6144, 8192 }; + + for (int size : ffdheSizes) { + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(paramGen); + + paramGen.init(size); + + try { + AlgorithmParameters params = paramGen.generateParameters(); + assertNotNull(params); + + /* Verify we can get DHParameterSpec from generated params */ + DHParameterSpec spec = + params.getParameterSpec(DHParameterSpec.class); + assertNotNull(spec); + assertNotNull(spec.getP()); + assertNotNull(spec.getG()); + + /* Verify the prime size matches what we requested */ + int actualSize = spec.getP().bitLength(); + assertEquals("Expected " + size + " bit params, got " + + actualSize, size, actualSize); + } + catch (RuntimeException e) { + /* Skip FFDHE sizes not compiled into native wolfSSL */ + if (e.getMessage() != null && + (e.getMessage().contains("group not available") || + e.getMessage().contains("Unsupported FFDHE group"))) { + continue; + } + throw e; + } + } + } + + @Test + public void testDHParameterGenerationFFDHESizesWithSecureRandom() + throws NoSuchProviderException, NoSuchAlgorithmException, + Exception { + + /* Test standard FFDHE sizes with SecureRandom */ + int[] ffdheSizes = { 2048, 3072, 4096, 6144, 8192 }; + + for (int size : ffdheSizes) { + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(paramGen); + + paramGen.init(size, rand); + + try { + AlgorithmParameters params = paramGen.generateParameters(); + assertNotNull(params); + + DHParameterSpec spec = + params.getParameterSpec(DHParameterSpec.class); + assertNotNull(spec); + assertNotNull(spec.getP()); + assertNotNull(spec.getG()); + + /* Verify the prime size matches what we requested */ + int actualSize = spec.getP().bitLength(); + assertEquals("Expected " + size + " bit params, got " + + actualSize, size, actualSize); + } + catch (RuntimeException e) { + /* Skip FFDHE sizes not compiled into native wolfSSL */ + if (e.getMessage() != null && + (e.getMessage().contains("group not available") || + e.getMessage().contains("Unsupported FFDHE group"))) { + continue; + } + throw e; + } + } + } + + @Test + public void testDHParameterGenerationNonStandardSizes() + throws NoSuchProviderException, NoSuchAlgorithmException, + Exception { + + /* Test non-standard sizes (requires dynamic generation). + * Skip in FIPS mode as FIPS 186-4 only allows 1024, 2048, + * and 3072-bit DH parameter generation */ + if (Fips.enabled) { + return; + } + + int[] nonStandardSizes = { 1024, 1536 }; + + for (int size : nonStandardSizes) { + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(paramGen); + + paramGen.init(size); + + try { + AlgorithmParameters params = paramGen.generateParameters(); + assertNotNull(params); + + DHParameterSpec spec = + params.getParameterSpec(DHParameterSpec.class); + assertNotNull(spec); + assertNotNull(spec.getP()); + assertNotNull(spec.getG()); + + /* Note: bit length may vary slightly for dynamically + * generated params */ + assertTrue(spec.getP().bitLength() >= size - 8 && + spec.getP().bitLength() <= size + 8); + } + catch (RuntimeException e) { + /* Dynamic parameter generation may not be supported for all + * sizes, especially in FIPS builds or when wolfSSL enforces + * minimum parameter sizes. Skip if generation fails. */ + if (e.getMessage() != null && + e.getMessage().contains("Bad function argument")) { + continue; + } + throw e; + } + } + } + + @Test + public void testDHParameterGenerationDefaultSize() + throws NoSuchProviderException, NoSuchAlgorithmException, + Exception { + + /* Test that default size is 2048 bits when no size is specified */ + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(paramGen); + + AlgorithmParameters params = paramGen.generateParameters(); + assertNotNull(params); + + DHParameterSpec spec = params.getParameterSpec(DHParameterSpec.class); + assertNotNull(spec); + assertEquals(2048, spec.getP().bitLength()); + } + + @Test + public void testDHParameterGenerationWithDHGenParameterSpec() + throws NoSuchProviderException, NoSuchAlgorithmException, Exception { + + /* Test that DHGenParameterSpec is supported and properly sets + * both prime size and exponent size */ + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(paramGen); + + /* Initialize with DHGenParameterSpec: 2048-bit prime, + * 256-bit exponent */ + DHGenParameterSpec genSpec = new DHGenParameterSpec(2048, 256); + paramGen.init(genSpec, rand); + + AlgorithmParameters params = paramGen.generateParameters(); + assertNotNull(params); + + DHParameterSpec spec = params.getParameterSpec(DHParameterSpec.class); + assertNotNull(spec); + assertNotNull(spec.getP()); + assertNotNull(spec.getG()); + assertEquals(2048, spec.getP().bitLength()); + assertEquals(256, spec.getL()); + } + + @Test + public void testDHParameterGenerationWithInvalidAlgorithmParameterSpec() + throws NoSuchProviderException, NoSuchAlgorithmException { + + /* Test that non-DHGenParameterSpec AlgorithmParameterSpec is + * rejected */ + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(paramGen); + + /* Try to initialize with a generic AlgorithmParameterSpec - + * should throw exception */ + try { + AlgorithmParameterSpec spec = new AlgorithmParameterSpec() {}; + paramGen.init(spec, rand); + + fail("AlgorithmParameterGenerator.init should throw " + + "InvalidAlgorithmParameterException when given " + + "non-DHGenParameterSpec"); + + } catch (InvalidAlgorithmParameterException e) { + /* expected */ + } + } + + @Test + public void testDHParameterGenerationWithDHGenParameterSpecMultipleSizes() + throws NoSuchProviderException, NoSuchAlgorithmException, Exception { + + /* Test DHGenParameterSpec with various FFDHE sizes and + * exponent sizes */ + int[][] testCases = { + {2048, 256}, + {3072, 256}, + {4096, 384}, + {2048, 160} + }; + + for (int[] testCase : testCases) { + int primeSize = testCase[0]; + int exponentSize = testCase[1]; + + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(paramGen); + + DHGenParameterSpec genSpec = + new DHGenParameterSpec(primeSize, exponentSize); + paramGen.init(genSpec, rand); + + try { + AlgorithmParameters params = paramGen.generateParameters(); + assertNotNull(params); + + DHParameterSpec spec = + params.getParameterSpec(DHParameterSpec.class); + assertNotNull(spec); + + /* Verify the prime size matches what was requested */ + int actualPrimeSize = spec.getP().bitLength(); + assertEquals("Expected " + primeSize + " bit params, got " + + actualPrimeSize, primeSize, actualPrimeSize); + + /* Exponent size should match what was requested */ + assertEquals(exponentSize, spec.getL()); + } + catch (RuntimeException e) { + /* Skip FFDHE sizes not compiled into native wolfSSL */ + if (e.getMessage() != null && + (e.getMessage().contains("group not available") || + e.getMessage().contains("Unsupported FFDHE group"))) { + continue; + } + throw e; + } + } + } + + @Test + public void testDHParameterGenerationWithDHGenParameterSpecNullRandom() + throws NoSuchProviderException, NoSuchAlgorithmException, Exception { + + /* Test that DHGenParameterSpec works with null SecureRandom + * (should use default) */ + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(paramGen); + + DHGenParameterSpec genSpec = new DHGenParameterSpec(2048, 256); + paramGen.init(genSpec, null); + + AlgorithmParameters params = paramGen.generateParameters(); + assertNotNull(params); + + DHParameterSpec spec = params.getParameterSpec(DHParameterSpec.class); + assertNotNull(spec); + assertEquals(2048, spec.getP().bitLength()); + assertEquals(256, spec.getL()); + } + + @Test + public void testDHParameterGenerationWithDHGenParameterSpecNullSpec() + throws NoSuchProviderException, NoSuchAlgorithmException { + + /* Test that null DHGenParameterSpec is rejected */ + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(paramGen); + + try { + paramGen.init(null, rand); + fail("AlgorithmParameterGenerator.init should throw " + + "InvalidAlgorithmParameterException when given null spec"); + } catch (InvalidAlgorithmParameterException e) { + /* expected */ + } + } + + @Test + public void testDHParameterGenerationInteropWithSunJCE() + throws Exception { + + /* Test interoperability with SunJCE provider. + * Generate parameters with wolfJCE and verify they work with + * standard Java DH key agreement */ + + AlgorithmParameterGenerator wolfParamGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(wolfParamGen); + + wolfParamGen.init(2048); + AlgorithmParameters wolfParams = wolfParamGen.generateParameters(); + assertNotNull(wolfParams); + + DHParameterSpec spec = + wolfParams.getParameterSpec(DHParameterSpec.class); + assertNotNull(spec); + + /* Try to create a SunJCE KeyPairGenerator with wolfJCE params */ + try { + KeyPairGenerator sunKpg = + KeyPairGenerator.getInstance("DH", "SunJCE"); + sunKpg.initialize(spec); + + /* Generate a key pair using SunJCE with wolfJCE params */ + KeyPair kp = sunKpg.generateKeyPair(); + assertNotNull(kp); + assertNotNull(kp.getPrivate()); + assertNotNull(kp.getPublic()); + + } catch (NoSuchProviderException e) { + /* SunJCE provider not available, skip interop test */ + System.out.println("\tSkipping SunJCE interop test, " + + "provider not available"); + } + } + + @Test + public void testDHParametersFromWolfJCEMatchStandardProvider() + throws Exception { + + /* Generate params with both providers and verify format + * compatibility */ + AlgorithmParameterGenerator wolfParamGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + assertNotNull(wolfParamGen); + + wolfParamGen.init(2048); + AlgorithmParameters wolfParams = wolfParamGen.generateParameters(); + assertNotNull(wolfParams); + + /* Get DER encoding from wolfJCE */ + byte[] wolfEncoded = wolfParams.getEncoded(); + assertNotNull(wolfEncoded); + assertTrue(wolfEncoded.length > 0); + + /* Try to parse with standard Java AlgorithmParameters */ + try { + AlgorithmParameters sunParams = + AlgorithmParameters.getInstance("DH", "SunJCE"); + sunParams.init(wolfEncoded); + + /* If we get here, SunJCE successfully parsed our encoding */ + DHParameterSpec sunSpec = + sunParams.getParameterSpec(DHParameterSpec.class); + assertNotNull(sunSpec); + + /* Verify the parameters match */ + DHParameterSpec wolfSpec = + wolfParams.getParameterSpec(DHParameterSpec.class); + assertEquals(wolfSpec.getP(), sunSpec.getP()); + assertEquals(wolfSpec.getG(), sunSpec.getG()); + + } catch (NoSuchProviderException e) { + /* SunJCE provider not available, skip interop test */ + System.out.println("\tSkipping SunJCE interop test, " + + "provider not available"); + } + } + + @Test + public void testDHParameterGenerationConsistency() + throws NoSuchProviderException, NoSuchAlgorithmException, + Exception { + + /* For FFDHE standard sizes, parameters should be deterministic + * (same size should produce same p and g) */ + AlgorithmParameterGenerator paramGen1 = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + AlgorithmParameterGenerator paramGen2 = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + + paramGen1.init(2048); + paramGen2.init(2048); + + AlgorithmParameters params1 = paramGen1.generateParameters(); + AlgorithmParameters params2 = paramGen2.generateParameters(); + + DHParameterSpec spec1 = + params1.getParameterSpec(DHParameterSpec.class); + DHParameterSpec spec2 = + params2.getParameterSpec(DHParameterSpec.class); + + /* For standard FFDHE_2048, p and g should be identical */ + assertEquals(spec1.getP(), spec2.getP()); + assertEquals(spec1.getG(), spec2.getG()); + } + + @Test + public void testDHGenParameterSpecInteropWithSunJCE() + throws Exception { + + /* Test that our DHGenParameterSpec behavior matches SunJCE. + * Both should accept DHGenParameterSpec and generate parameters + * with the specified prime size and exponent size. */ + + try { + /* Generate with wolfJCE using DHGenParameterSpec */ + AlgorithmParameterGenerator wolfParamGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + DHGenParameterSpec genSpec = new DHGenParameterSpec(2048, 256); + wolfParamGen.init(genSpec, rand); + AlgorithmParameters wolfParams = wolfParamGen.generateParameters(); + + DHParameterSpec wolfSpec = + wolfParams.getParameterSpec(DHParameterSpec.class); + assertEquals(2048, wolfSpec.getP().bitLength()); + assertEquals(256, wolfSpec.getL()); + + /* Verify SunJCE accepts the same DHGenParameterSpec format */ + AlgorithmParameterGenerator sunParamGen = + AlgorithmParameterGenerator.getInstance("DH", "SunJCE"); + sunParamGen.init(genSpec, rand); + AlgorithmParameters sunParams = sunParamGen.generateParameters(); + + DHParameterSpec sunSpec = + sunParams.getParameterSpec(DHParameterSpec.class); + assertEquals(2048, sunSpec.getP().bitLength()); + assertEquals(256, sunSpec.getL()); + + } catch (NoSuchProviderException e) { + /* SunJCE provider not available, skip interop test */ + System.out.println("\tSkipping SunJCE DHGenParameterSpec " + + "interop test, provider not available"); + } + } +} + diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptAlgorithmParametersTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptAlgorithmParametersTest.java new file mode 100644 index 00000000..eb2e8f09 --- /dev/null +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptAlgorithmParametersTest.java @@ -0,0 +1,602 @@ +/* WolfCryptAlgorithmParametersTest.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 org.junit.Test; +import org.junit.BeforeClass; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.Security; +import java.security.Provider; +import java.security.AlgorithmParameters; +import java.security.AlgorithmParameterGenerator; +import java.security.NoSuchProviderException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidParameterSpecException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.IvParameterSpec; + +import com.wolfssl.provider.jce.WolfCryptProvider; + +public class WolfCryptAlgorithmParametersTest { + + @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 testProviderInstallationAtRuntime() + throws NoSuchAlgorithmException, NoSuchProviderException { + + System.out.println("JCE WolfCryptAlgorithmParametersTest Class"); + + /* Install wolfJCE provider at runtime */ + Security.insertProviderAt(new WolfCryptProvider(), 1); + + Provider p = Security.getProvider("wolfJCE"); + assertNotNull(p); + } + + @Test + public void testGetAlgorithmParametersFromProvider() + throws NoSuchProviderException, NoSuchAlgorithmException { + + AlgorithmParameters params; + + /* DH should be available */ + params = AlgorithmParameters.getInstance("DH", "wolfJCE"); + assertNotNull(params); + + /* Getting a garbage algorithm should throw an exception */ + try { + params = AlgorithmParameters.getInstance( + "NotValid", "wolfJCE"); + + fail("AlgorithmParameters.getInstance should throw " + + "NoSuchAlgorithmException when given bad algorithm value"); + + } catch (NoSuchAlgorithmException e) { + /* expected */ + } + } + + @Test + public void testDHParametersInitWithDHParameterSpec() + throws Exception { + + /* Create known DH parameters */ + BigInteger p = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16); + BigInteger g = BigInteger.valueOf(2); + + DHParameterSpec spec = new DHParameterSpec(p, g); + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + assertNotNull(params); + + params.init(spec); + + /* Retrieve the parameters back */ + DHParameterSpec retrievedSpec = + params.getParameterSpec(DHParameterSpec.class); + assertNotNull(retrievedSpec); + assertEquals(p, retrievedSpec.getP()); + assertEquals(g, retrievedSpec.getG()); + } + + @Test + public void testDHParametersInitWithDHParameterSpecIncludingL() + throws Exception { + + BigInteger p = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16); + BigInteger g = BigInteger.valueOf(2); + int l = 256; + + DHParameterSpec spec = new DHParameterSpec(p, g, l); + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params.init(spec); + + DHParameterSpec retrievedSpec = + params.getParameterSpec(DHParameterSpec.class); + assertEquals(p, retrievedSpec.getP()); + assertEquals(g, retrievedSpec.getG()); + assertEquals(l, retrievedSpec.getL()); + } + + @Test + public void testDHParametersInitWithInvalidParameterSpec() + throws Exception { + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + + /* Try to initialize with wrong type of ParameterSpec */ + try { + AlgorithmParameterSpec invalidSpec = + new AlgorithmParameterSpec() {}; + params.init(invalidSpec); + + fail("AlgorithmParameters.init should throw " + + "InvalidParameterSpecException when given wrong spec type"); + + } catch (InvalidParameterSpecException e) { + /* expected */ + } + } + + @Test + public void testDHParametersGetParameterSpecWithWrongClass() + throws Exception { + + BigInteger p = new BigInteger("123456789"); + BigInteger g = BigInteger.valueOf(2); + DHParameterSpec spec = new DHParameterSpec(p, g); + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params.init(spec); + + /* Try to get base AlgorithmParameterSpec class - this succeeds + * because DHParameterSpec implements AlgorithmParameterSpec + * and the implementation correctly returns a DHParameterSpec which + * can be cast to AlgorithmParameterSpec */ + AlgorithmParameterSpec retrievedSpec = + params.getParameterSpec(AlgorithmParameterSpec.class); + assertNotNull(retrievedSpec); + assertTrue(retrievedSpec instanceof DHParameterSpec); + + /* Try with a completely wrong spec type that DH doesn't support */ + try { + IvParameterSpec ivSpec = + params.getParameterSpec(IvParameterSpec.class); + + fail("AlgorithmParameters.getParameterSpec should throw " + + "InvalidParameterSpecException when given unsupported class"); + + } catch (InvalidParameterSpecException e) { + /* expected */ + } + } + + @Test + public void testDHParametersGetParameterSpecWithNull() + throws Exception { + + BigInteger p = new BigInteger("123456789"); + BigInteger g = BigInteger.valueOf(2); + DHParameterSpec spec = new DHParameterSpec(p, g); + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params.init(spec); + + /* Try to get ParameterSpec with null class */ + try { + params.getParameterSpec(null); + + fail("AlgorithmParameters.getParameterSpec should throw " + + "InvalidParameterSpecException when given null"); + + } catch (InvalidParameterSpecException e) { + /* expected */ + } + } + + @Test + public void testDHParametersGetParameterSpecBeforeInit() + throws Exception { + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + + /* Try to get ParameterSpec before initialization */ + try { + params.getParameterSpec(DHParameterSpec.class); + + fail("AlgorithmParameters.getParameterSpec should throw " + + "InvalidParameterSpecException when not initialized"); + + } catch (InvalidParameterSpecException e) { + /* expected */ + } + } + + @Test + public void testDHParametersEncodingDER() + throws Exception { + + BigInteger p = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16); + BigInteger g = BigInteger.valueOf(2); + + DHParameterSpec spec = new DHParameterSpec(p, g); + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params.init(spec); + + /* Get DER encoding */ + byte[] encoded = params.getEncoded(); + assertNotNull(encoded); + assertTrue(encoded.length > 0); + + /* Verify it starts with SEQUENCE tag (0x30) */ + assertEquals(0x30, encoded[0] & 0xFF); + + /* Create new AlgorithmParameters and init with encoded bytes */ + AlgorithmParameters params2 = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params2.init(encoded); + + DHParameterSpec spec2 = + params2.getParameterSpec(DHParameterSpec.class); + assertEquals(p, spec2.getP()); + assertEquals(g, spec2.getG()); + } + + @Test + public void testDHParametersEncodingWithFormat() + throws Exception { + + BigInteger p = new BigInteger("123456789"); + BigInteger g = BigInteger.valueOf(2); + DHParameterSpec spec = new DHParameterSpec(p, g); + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params.init(spec); + + /* Test ASN.1 format */ + byte[] asnEncoded = params.getEncoded("ASN.1"); + assertNotNull(asnEncoded); + + /* Test DER format */ + byte[] derEncoded = params.getEncoded("DER"); + assertNotNull(derEncoded); + + /* ASN.1 and DER should be the same for DH params */ + assertTrue(Arrays.equals(asnEncoded, derEncoded)); + + /* Test case insensitivity */ + byte[] lowerEncoded = params.getEncoded("asn.1"); + assertTrue(Arrays.equals(asnEncoded, lowerEncoded)); + } + + @Test + public void testDHParametersEncodingUnsupportedFormat() + throws Exception { + + BigInteger p = new BigInteger("123456789"); + BigInteger g = BigInteger.valueOf(2); + DHParameterSpec spec = new DHParameterSpec(p, g); + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params.init(spec); + + /* Try unsupported format */ + try { + params.getEncoded("PEM"); + fail("AlgorithmParameters.getEncoded should throw " + + "IOException for unsupported format"); + } catch (IOException e) { + /* expected */ + } + } + + @Test + public void testDHParametersEncodingBeforeInit() + throws Exception { + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + + /* Try to encode before initialization */ + try { + params.getEncoded(); + fail("AlgorithmParameters.getEncoded should throw " + + "IOException when not initialized"); + } catch (IOException e) { + /* expected */ + } + } + + @Test + public void testDHParametersInitWithDERBytes() + throws Exception { + + /* Generate parameters to get valid DER encoding */ + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + paramGen.init(2048); + AlgorithmParameters params1 = paramGen.generateParameters(); + byte[] encoded = params1.getEncoded(); + + /* Create new AlgorithmParameters and init with DER bytes */ + AlgorithmParameters params2 = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params2.init(encoded); + + /* Verify we get the same parameters back */ + DHParameterSpec spec1 = + params1.getParameterSpec(DHParameterSpec.class); + DHParameterSpec spec2 = + params2.getParameterSpec(DHParameterSpec.class); + + assertEquals(spec1.getP(), spec2.getP()); + assertEquals(spec1.getG(), spec2.getG()); + } + + @Test + public void testDHParametersInitWithDERBytesAndFormat() + throws Exception { + + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + paramGen.init(2048); + AlgorithmParameters params1 = paramGen.generateParameters(); + byte[] encoded = params1.getEncoded(); + + /* Test init with explicit format */ + AlgorithmParameters params2 = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params2.init(encoded, "DER"); + + DHParameterSpec spec1 = + params1.getParameterSpec(DHParameterSpec.class); + DHParameterSpec spec2 = + params2.getParameterSpec(DHParameterSpec.class); + + assertEquals(spec1.getP(), spec2.getP()); + assertEquals(spec1.getG(), spec2.getG()); + + /* Test with ASN.1 format */ + AlgorithmParameters params3 = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params3.init(encoded, "ASN.1"); + + DHParameterSpec spec3 = + params3.getParameterSpec(DHParameterSpec.class); + assertEquals(spec1.getP(), spec3.getP()); + assertEquals(spec1.getG(), spec3.getG()); + } + + @Test + public void testDHParametersInitWithInvalidDERBytes() + throws Exception { + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + + /* Try to init with invalid DER bytes */ + byte[] invalidDER = new byte[] { 0x01, 0x02, 0x03 }; + + try { + params.init(invalidDER); + fail("AlgorithmParameters.init should throw " + + "IOException for invalid DER encoding"); + } catch (IOException e) { + /* expected */ + } + } + + @Test + public void testDHParametersInitWithUnsupportedFormat() + throws Exception { + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + + byte[] data = new byte[] { 0x30, 0x00 }; + + try { + params.init(data, "PEM"); + fail("AlgorithmParameters.init should throw " + + "IOException for unsupported format"); + } catch (IOException e) { + /* expected */ + } + } + + @Test + public void testDHParametersToString() + throws Exception { + + BigInteger p = new BigInteger("123456789"); + BigInteger g = BigInteger.valueOf(2); + DHParameterSpec spec = new DHParameterSpec(p, g); + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params.init(spec); + + String str = params.toString(); + assertNotNull(str); + assertTrue(str.contains("DH Parameters")); + assertTrue(str.contains("p:")); + assertTrue(str.contains("g:")); + } + + @Test + public void testDHParametersToStringBeforeInit() + throws Exception { + + AlgorithmParameters params = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + + /* Note: The standard Java AlgorithmParameters.toString() may return + * null or an implementation-specific string. The JCA specification + * doesn't mandate a specific format. Our engineToString() method + * returns a proper string, but the standard Java wrapper doesn't + * always call it. Just verify toString() doesn't throw an exception. */ + String str = params.toString(); + /* No assertions - just making sure it doesn't throw */ + } + + @Test + public void testDHParametersInteropWithSunJCE() + throws Exception { + + /* Generate parameters with wolfJCE */ + AlgorithmParameterGenerator wolfParamGen = + AlgorithmParameterGenerator.getInstance("DH", "wolfJCE"); + wolfParamGen.init(2048); + AlgorithmParameters wolfParams = wolfParamGen.generateParameters(); + + byte[] wolfEncoded = wolfParams.getEncoded(); + + /* Try to parse with SunJCE */ + try { + AlgorithmParameters sunParams = + AlgorithmParameters.getInstance("DH", "SunJCE"); + sunParams.init(wolfEncoded); + + /* Verify parameters match */ + DHParameterSpec wolfSpec = + wolfParams.getParameterSpec(DHParameterSpec.class); + DHParameterSpec sunSpec = + sunParams.getParameterSpec(DHParameterSpec.class); + + assertEquals(wolfSpec.getP(), sunSpec.getP()); + assertEquals(wolfSpec.getG(), sunSpec.getG()); + + /* Now test the reverse: SunJCE -> wolfJCE */ + byte[] sunEncoded = sunParams.getEncoded(); + + AlgorithmParameters wolfParams2 = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + wolfParams2.init(sunEncoded); + + DHParameterSpec wolfSpec2 = + wolfParams2.getParameterSpec(DHParameterSpec.class); + + assertEquals(sunSpec.getP(), wolfSpec2.getP()); + assertEquals(sunSpec.getG(), wolfSpec2.getG()); + + } catch (NoSuchProviderException e) { + /* SunJCE provider not available, skip interop test */ + System.out.println("\tSkipping SunJCE interop test, " + + "provider not available"); + } + } + + @Test + public void testDHParametersRoundTrip() + throws Exception { + + /* Test multiple round trips: spec -> params -> encoded -> + * params -> spec. + * NOTE: The 'l' parameter is not preserved through encoding/decoding + * since standard DH ASN.1 encoding only includes p and g. */ + BigInteger p = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16); + BigInteger g = BigInteger.valueOf(2); + + DHParameterSpec originalSpec = new DHParameterSpec(p, g); + + /* Round trip 1 */ + AlgorithmParameters params1 = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params1.init(originalSpec); + + byte[] encoded1 = params1.getEncoded(); + + /* Round trip 2 */ + AlgorithmParameters params2 = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params2.init(encoded1); + + byte[] encoded2 = params2.getEncoded(); + + /* Round trip 3 */ + AlgorithmParameters params3 = + AlgorithmParameters.getInstance("DH", "wolfJCE"); + params3.init(encoded2); + + DHParameterSpec finalSpec = + params3.getParameterSpec(DHParameterSpec.class); + + /* Verify all parameters match original (p and g only, not l) */ + assertEquals(originalSpec.getP(), finalSpec.getP()); + assertEquals(originalSpec.getG(), finalSpec.getG()); + + /* Verify encodings are identical */ + assertTrue(Arrays.equals(encoded1, encoded2)); + } +} + diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java new file mode 100644 index 00000000..37de3aa9 --- /dev/null +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java @@ -0,0 +1,940 @@ +/* WolfCryptDHKeyFactoryTest.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.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import javax.crypto.interfaces.DHPrivateKey; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.DHPrivateKeySpec; +import javax.crypto.spec.DHPublicKeySpec; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.wolfssl.provider.jce.WolfCryptProvider; +import com.wolfssl.wolfcrypt.FeatureDetect; + +/** + * JUnit4 test cases for WolfCryptDHKeyFactory + */ +public class WolfCryptDHKeyFactoryTest { + + /* Standard DH key sizes to test */ + private static int[] keySizes = {512, 1024, 2048}; + + private static ArrayList enabledKeySizes = + 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 WolfCryptDHKeyFactory Class"); + + if (!FeatureDetect.DhEnabled()) { + System.out.println("DH support not compiled in, skipping tests"); + return; + } + + /* Build list of enabled key sizes */ + for (int i = 0; i < keySizes.length; i++) { + try { + /* Test if this key size works */ + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(keySizes[i]); + KeyPair kp = kpg.generateKeyPair(); + + if (kp != null) { + enabledKeySizes.add(keySizes[i]); + } + + } catch (Exception e) { + System.out.println("Skipping DH key size: " + keySizes[i] + + " bits - " + e.getMessage()); + } + } + } + + @Test + public void testDHKeyFactoryInstantiation() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Test that we can get a DH KeyFactory instance */ + KeyFactory kf = KeyFactory.getInstance("DH", "wolfJCE"); + assertNotNull("KeyFactory should not be null", kf); + assertEquals("Provider should be wolfJCE", "wolfJCE", + kf.getProvider().getName()); + + /* Test DiffieHellman alias */ + KeyFactory kf2 = KeyFactory.getInstance("DiffieHellman", "wolfJCE"); + assertNotNull("KeyFactory should not be null", kf2); + assertEquals("Provider should be wolfJCE", "wolfJCE", + kf2.getProvider().getName()); + } + + @Test + public void testPKCS8PrivateKeyConversion() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Generate a test key pair using wolfJCE */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + DHPrivateKey privKey = (DHPrivateKey) 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("DH", "wolfJCE"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + PrivateKey convertedKey = kf.generatePrivate(keySpec); + + assertNotNull("Converted key should not be null", convertedKey); + assertTrue("Should be DHPrivateKey", + convertedKey instanceof DHPrivateKey); + + /* 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.DhEnabled()) { + return; + } + + /* Generate a test key pair using wolfJCE */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + DHPublicKey pubKey = (DHPublicKey) 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("DH", "wolfJCE"); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); + PublicKey convertedKey = kf.generatePublic(keySpec); + + assertNotNull("Converted key should not be null", convertedKey); + assertTrue("Should be DHPublicKey", + convertedKey instanceof DHPublicKey); + + /* 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 testDHPrivateKeySpecConversion() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Generate a test key pair using system provider for reference */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + DHPrivateKey privKey = (DHPrivateKey) kp.getPrivate(); + + /* Extract DHPrivateKeySpec using system KeyFactory */ + KeyFactory sysKF = KeyFactory.getInstance("DH"); + DHPrivateKeySpec keySpec = sysKF.getKeySpec(privKey, + DHPrivateKeySpec.class); + + /* Convert using our KeyFactory */ + KeyFactory wolfKF = KeyFactory.getInstance("DH", "wolfJCE"); + PrivateKey convertedKey = wolfKF.generatePrivate(keySpec); + + assertNotNull("Converted key should not be null", convertedKey); + assertTrue("Should be DHPrivateKey", + convertedKey instanceof DHPrivateKey); + + /* Verify key parameters match */ + DHPrivateKey convertedDHKey = (DHPrivateKey) convertedKey; + assertEquals("Private key values should match", + privKey.getX(), convertedDHKey.getX()); + assertEquals("DH parameter p should match", + privKey.getParams().getP(), convertedDHKey.getParams().getP()); + assertEquals("DH parameter g should match", + privKey.getParams().getG(), convertedDHKey.getParams().getG()); + } + + @Test + public void testDHPublicKeySpecConversion() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Generate a test key pair using system provider for reference */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + DHPublicKey pubKey = (DHPublicKey) kp.getPublic(); + + /* Extract DHPublicKeySpec using system KeyFactory */ + KeyFactory sysKF = KeyFactory.getInstance("DH"); + DHPublicKeySpec keySpec = sysKF.getKeySpec(pubKey, + DHPublicKeySpec.class); + + /* Convert using our KeyFactory */ + KeyFactory wolfKF = KeyFactory.getInstance("DH", "wolfJCE"); + PublicKey convertedKey = wolfKF.generatePublic(keySpec); + + assertNotNull("Converted key should not be null", convertedKey); + assertTrue("Should be DHPublicKey", + convertedKey instanceof DHPublicKey); + + /* Verify key parameters match */ + DHPublicKey convertedDHKey = (DHPublicKey) convertedKey; + assertEquals("Public key values should match", + pubKey.getY(), convertedDHKey.getY()); + assertEquals("DH parameter p should match", + pubKey.getParams().getP(), convertedDHKey.getParams().getP()); + assertEquals("DH parameter g should match", + pubKey.getParams().getG(), convertedDHKey.getParams().getG()); + } + + @Test + public void testKeySpecExtraction() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Generate a test key pair using wolfJCE */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + KeyFactory kf = KeyFactory.getInstance("DH", "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 DHPrivateKeySpec extraction */ + DHPrivateKeySpec dhPrivSpec = kf.getKeySpec(kp.getPrivate(), + DHPrivateKeySpec.class); + assertNotNull("DHPrivateKeySpec should not be null", dhPrivSpec); + + /* Test DHPublicKeySpec extraction */ + DHPublicKeySpec dhPubSpec = kf.getKeySpec(kp.getPublic(), + DHPublicKeySpec.class); + assertNotNull("DHPublicKeySpec should not be null", dhPubSpec); + } + + @Test + public void testKeyTranslation() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Generate a test key pair using system provider */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH"); + kpg.initialize(2048); + KeyPair systemKP = kpg.generateKeyPair(); + + /* Translate keys using wolfJCE KeyFactory */ + KeyFactory wolfKF = KeyFactory.getInstance("DH", "wolfJCE"); + + PrivateKey translatedPriv = + (PrivateKey)wolfKF.translateKey(systemKP.getPrivate()); + assertNotNull("Translated private key should not be null", + translatedPriv); + assertTrue("Should be DHPrivateKey", + translatedPriv instanceof DHPrivateKey); + + PublicKey translatedPub = + (PublicKey)wolfKF.translateKey(systemKP.getPublic()); + assertNotNull("Translated public key should not be null", + translatedPub); + assertTrue("Should be DHPublicKey", + translatedPub instanceof DHPublicKey); + + /* 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.DhEnabled()) { + return; + } + + /* Generate a test key pair using wolfJCE */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair originalKP = kpg.generateKeyPair(); + + KeyFactory kf = KeyFactory.getInstance("DH", "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 testMultipleKeySizes() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + KeyFactory kf = KeyFactory.getInstance("DH", "wolfJCE"); + + for (Integer keySize : enabledKeySizes) { + try { + /* Generate key pair for this key size */ + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(keySize); + KeyPair kp = kpg.generateKeyPair(); + + /* Test conversion works for this key size */ + 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 " + + keySize + " bits", convertedPriv); + assertNotNull("Converted public key should not be null for " + + keySize + " bits", convertedPub); + + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to test key size " + keySize + ": " + + e.getMessage()); + } + } + } + + @Test + public void testInvalidKeySpecs() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + KeyFactory kf = KeyFactory.getInstance("DH", "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 testDHPrivateKeySpecConversionWithoutSunJCE() + throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Remove SunJCE provider temporarily if present */ + Provider sunJCE = Security.getProvider("SunJCE"); + if (sunJCE != null) { + Security.removeProvider("SunJCE"); + } + + try { + /* Generate key using wolfJCE only */ + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + DHPrivateKey privKey = (DHPrivateKey) kp.getPrivate(); + + /* Extract DHPrivateKeySpec and convert back */ + KeyFactory wolfKF = KeyFactory.getInstance("DH", "wolfJCE"); + DHPrivateKeySpec keySpec = + wolfKF.getKeySpec(privKey, DHPrivateKeySpec.class); + PrivateKey convertedKey = wolfKF.generatePrivate(keySpec); + + /* Verify conversion worked */ + assertNotNull("Converted key should not be null", convertedKey); + assertTrue("Should be DHPrivateKey", + convertedKey instanceof DHPrivateKey); + + DHPrivateKey convertedDHKey = (DHPrivateKey) convertedKey; + assertEquals("Private key values should match", + privKey.getX(), convertedDHKey.getX()); + + } finally { + /* Restore SunJCE provider if it was present */ + if (sunJCE != null) { + Security.addProvider(sunJCE); + } + } + } + + @Test + public void testBigIntegerEdgeCases() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Generate a reference key to get the DHParameterSpec */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair refKP = kpg.generateKeyPair(); + DHPrivateKey refPrivKey = (DHPrivateKey) refKP.getPrivate(); + DHParameterSpec params = refPrivKey.getParams(); + + KeyFactory wolfKF = KeyFactory.getInstance("DH", "wolfJCE"); + + /* Test case 1: Private key with leading zeros (small value) */ + BigInteger smallPrivateValue = BigInteger.valueOf(1); + DHPrivateKeySpec smallKeySpec = + new DHPrivateKeySpec(smallPrivateValue, params.getP(), + params.getG()); + PrivateKey smallKey = wolfKF.generatePrivate(smallKeySpec); + assertNotNull("Small private key should be created", smallKey); + assertTrue("Should be DHPrivateKey", smallKey instanceof DHPrivateKey); + + /* Test case 2: Private key with MSB set + * (requires sign bit handling) */ + BigInteger largeMSBValue = + new BigInteger("FF00000000000000000000000000000000000000" + + "00000000000000000000000000000001", 16); + DHPrivateKeySpec largeMSBKeySpec = + new DHPrivateKeySpec(largeMSBValue, params.getP(), params.getG()); + + try { + PrivateKey largeMSBKey = wolfKF.generatePrivate(largeMSBKeySpec); + assertNotNull("Large MSB private key should be created", + largeMSBKey); + assertTrue("Should be DHPrivateKey", + largeMSBKey instanceof DHPrivateKey); + + } catch (InvalidKeySpecException e) { + /* This might fail if the value is invalid for DH, + * which is expected */ + assertTrue("Error should be crypto-related", + e.getMessage().contains("too large") || + e.getMessage().contains("positive") || + e.getMessage().contains("Invalid")); + } + + /* Test case 3: Zero private key (should fail) */ + BigInteger zeroPrivateValue = BigInteger.ZERO; + DHPrivateKeySpec zeroKeySpec = + new DHPrivateKeySpec(zeroPrivateValue, params.getP(), + params.getG()); + + 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); + DHPrivateKeySpec negativeKeySpec = + new DHPrivateKeySpec(negativePrivateValue, params.getP(), + params.getG()); + + 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 testParameterValidation() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + KeyFactory wolfKF = KeyFactory.getInstance("DH", "wolfJCE"); + + /* Generate a reference key to get valid DH parameters */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + DHPrivateKey privKey = (DHPrivateKey) kp.getPrivate(); + DHParameterSpec params = privKey.getParams(); + + /* Test null private value */ + try { + DHPrivateKeySpec nullPrivateSpec = + new DHPrivateKeySpec(null, params.getP(), params.getG()); + wolfKF.generatePrivate(nullPrivateSpec); + fail("Should throw exception for null private value"); + + } catch (Exception e) { + /* Expected - either NPE or InvalidKeySpecException */ + assertTrue("Should be NPE or InvalidKeySpecException", + e instanceof NullPointerException || + e instanceof InvalidKeySpecException); + } + + /* Test null p parameter */ + try { + DHPrivateKeySpec nullPSpec = + new DHPrivateKeySpec(BigInteger.valueOf(100), null, + params.getG()); + wolfKF.generatePrivate(nullPSpec); + fail("Should throw exception for null p parameter"); + + } catch (Exception e) { + /* Expected - either NPE or InvalidKeySpecException */ + assertTrue("Should be NPE or InvalidKeySpecException", + e instanceof NullPointerException || + e instanceof InvalidKeySpecException); + } + + /* Test null g parameter */ + try { + DHPrivateKeySpec nullGSpec = + new DHPrivateKeySpec(BigInteger.valueOf(100), params.getP(), + null); + wolfKF.generatePrivate(nullGSpec); + fail("Should throw exception for null g parameter"); + + } catch (Exception e) { + /* Expected - either NPE or InvalidKeySpecException */ + assertTrue("Should be NPE or InvalidKeySpecException", + e instanceof NullPointerException || + e instanceof InvalidKeySpecException); + } + } + + @Test + public void testPrivateKeyBoundaryValues() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Generate a reference key to get the DHParameterSpec */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair refKP = kpg.generateKeyPair(); + DHPrivateKey refPrivKey = (DHPrivateKey) refKP.getPrivate(); + DHParameterSpec params = refPrivKey.getParams(); + + KeyFactory wolfKF = KeyFactory.getInstance("DH", "wolfJCE"); + + /* Test minimum valid private key (1) */ + BigInteger minValue = BigInteger.ONE; + DHPrivateKeySpec minKeySpec = new DHPrivateKeySpec(minValue, + params.getP(), params.getG()); + PrivateKey minKey = wolfKF.generatePrivate(minKeySpec); + assertNotNull("Minimum private key should be created", minKey); + + DHPrivateKey minDHKey = (DHPrivateKey) minKey; + assertEquals("Private key value should match", minValue, + minDHKey.getX()); + + /* Test very large value that should be invalid */ + BigInteger veryLargeValue = + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16); + DHPrivateKeySpec largeKeySpec = + new DHPrivateKeySpec(veryLargeValue, params.getP(), + params.getG()); + try { + wolfKF.generatePrivate(largeKeySpec); + /* If this succeeds, the value might be valid for this key size */ + + } catch (InvalidKeySpecException e) { + /* Check for valid error messages */ + assertTrue("Error should be crypto-related: " + e.getMessage(), + e.getMessage().contains("too large") || + e.getMessage().contains("large") || + e.getMessage().contains("size") || + e.getMessage().contains("Invalid") || + e.getMessage().contains("key value") || + e.getMessage().contains("not valid") || + e.getMessage().contains("error")); + } + } + + @Test + public void testMemoryCleanup() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + KeyFactory wolfKF = KeyFactory.getInstance("DH", "wolfJCE"); + + /* Generate many keys to test for memory leaks */ + for (int i = 0; i < 50; i++) { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + DHPrivateKey privKey = (DHPrivateKey) kp.getPrivate(); + DHPrivateKeySpec keySpec = + new DHPrivateKeySpec(privKey.getX(), privKey.getParams().getP(), + privKey.getParams().getG()); + + 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.DhEnabled()) { + return; + } + + /* Generate key using standard approach */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair originalKP = kpg.generateKeyPair(); + DHPrivateKey originalPrivKey = (DHPrivateKey) originalKP.getPrivate(); + + KeyFactory wolfKF = KeyFactory.getInstance("DH", "wolfJCE"); + + /* Test that DHPrivateKeySpec conversion produces consistent + * results */ + DHPrivateKeySpec keySpec = new DHPrivateKeySpec(originalPrivKey.getX(), + originalPrivKey.getParams().getP(), + originalPrivKey.getParams().getG()); + 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 */ + DHPrivateKey convertedDHKey = (DHPrivateKey) convertedKey1; + assertEquals("Private values should match", + originalPrivKey.getX(), convertedDHKey.getX()); + } + + @Test + public void testErrorHandling() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + KeyFactory wolfKF = KeyFactory.getInstance("DH", "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")); + } + + /* Generate reference key for valid parameters */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + DHPrivateKey privKey = (DHPrivateKey) kp.getPrivate(); + DHParameterSpec params = privKey.getParams(); + + /* Test zero private value (should be rejected) */ + try { + DHPrivateKeySpec zeroSpec = new DHPrivateKeySpec(BigInteger.ZERO, + params.getP(), params.getG()); + 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") || + e.getMessage().contains("positive")); + } + + /* Test negative private value (should be rejected) */ + try { + DHPrivateKeySpec negativeSpec = + new DHPrivateKeySpec(BigInteger.valueOf(-1), + params.getP(), params.getG()); + 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") || + e.getMessage().contains("positive")); + } + } + + @Test + public void testInteroperabilityWithSunJCE() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Generate key pair using SunJCE */ + KeyPairGenerator sunKpg = KeyPairGenerator.getInstance("DH"); + sunKpg.initialize(2048); + KeyPair sunKP = sunKpg.generateKeyPair(); + + /* Convert to wolfJCE using KeyFactory.translateKey() */ + KeyFactory wolfKF = KeyFactory.getInstance("DH", "wolfJCE"); + PrivateKey wolfPriv = (PrivateKey)wolfKF.translateKey( + sunKP.getPrivate()); + PublicKey wolfPub = (PublicKey)wolfKF.translateKey( + sunKP.getPublic()); + + assertNotNull("Translated private key should not be null", wolfPriv); + assertNotNull("Translated public key should not be null", wolfPub); + assertTrue("Should be DHPrivateKey", + wolfPriv instanceof DHPrivateKey); + assertTrue("Should be DHPublicKey", wolfPub instanceof DHPublicKey); + + /* Verify parameters match */ + DHPrivateKey sunPrivKey = (DHPrivateKey)sunKP.getPrivate(); + DHPrivateKey wolfPrivKey = (DHPrivateKey)wolfPriv; + assertEquals("Private values should match", + sunPrivKey.getX(), wolfPrivKey.getX()); + + /* Generate key pair using wolfJCE and convert to SunJCE specs */ + KeyPairGenerator wolfKpg = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + wolfKpg.initialize(2048); + KeyPair wolfKP = wolfKpg.generateKeyPair(); + + /* Extract KeySpecs that SunJCE can use */ + DHPrivateKeySpec wolfPrivSpec = + wolfKF.getKeySpec(wolfKP.getPrivate(), DHPrivateKeySpec.class); + DHPublicKeySpec wolfPubSpec = + wolfKF.getKeySpec(wolfKP.getPublic(), DHPublicKeySpec.class); + + /* Create SunJCE keys from wolfJCE KeySpecs */ + KeyFactory sunKF = KeyFactory.getInstance("DH"); + PrivateKey sunPrivFromWolf = sunKF.generatePrivate(wolfPrivSpec); + PublicKey sunPubFromWolf = sunKF.generatePublic(wolfPubSpec); + + assertNotNull("SunJCE private key from wolfJCE spec should not " + + "be null", sunPrivFromWolf); + assertNotNull("SunJCE public key from wolfJCE spec should not " + + "be null", sunPubFromWolf); + + /* Verify round-trip conversion */ + DHPrivateKey originalWolfPriv = (DHPrivateKey)wolfKP.getPrivate(); + DHPrivateKey sunPrivConverted = (DHPrivateKey)sunPrivFromWolf; + assertEquals("Private values should match after round-trip", + originalWolfPriv.getX(), sunPrivConverted.getX()); + } + + @Test + public void testRoundTripWithDHKeySpec() throws Exception { + + if (!FeatureDetect.DhEnabled()) { + return; + } + + /* Generate a test key pair using wolfJCE */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + KeyPair originalKP = kpg.generateKeyPair(); + + DHPrivateKey originalPriv = (DHPrivateKey)originalKP.getPrivate(); + DHPublicKey originalPub = (DHPublicKey)originalKP.getPublic(); + + KeyFactory kf = KeyFactory.getInstance("DH", "wolfJCE"); + + /* Round trip private key: Key -> DHPrivateKeySpec -> Key */ + DHPrivateKeySpec privSpec = new DHPrivateKeySpec( + originalPriv.getX(), + originalPriv.getParams().getP(), + originalPriv.getParams().getG()); + PrivateKey roundTripPriv = kf.generatePrivate(privSpec); + + /* Round trip public key: Key -> DHPublicKeySpec -> Key */ + DHPublicKeySpec pubSpec = new DHPublicKeySpec( + originalPub.getY(), + originalPub.getParams().getP(), + originalPub.getParams().getG()); + PublicKey roundTripPub = kf.generatePublic(pubSpec); + + /* Verify the round trip worked */ + DHPrivateKey roundTripPrivDH = (DHPrivateKey)roundTripPriv; + DHPublicKey roundTripPubDH = (DHPublicKey)roundTripPub; + + assertEquals("Private key values should match after round trip", + originalPriv.getX(), roundTripPrivDH.getX()); + assertEquals("Public key values should match after round trip", + originalPub.getY(), roundTripPubDH.getY()); + assertEquals("Private key p should match", + originalPriv.getParams().getP(), + roundTripPrivDH.getParams().getP()); + assertEquals("Public key p should match", + originalPub.getParams().getP(), roundTripPubDH.getParams().getP()); + } +} + diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyAgreementTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyAgreementTest.java index c264835e..94c23621 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyAgreementTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyAgreementTest.java @@ -39,6 +39,8 @@ import javax.crypto.KeyAgreement; import javax.crypto.ShortBufferException; +import javax.crypto.SecretKey; +import javax.crypto.Cipher; import javax.crypto.spec.DHParameterSpec; import java.security.Security; @@ -56,6 +58,7 @@ import java.security.spec.ECGenParameterSpec; import com.wolfssl.wolfcrypt.Ecc; +import com.wolfssl.wolfcrypt.Fips; import com.wolfssl.provider.jce.WolfCryptProvider; public class WolfCryptKeyAgreementTest { @@ -184,11 +187,33 @@ public void testDHKeyAgreement() InvalidParameterSpecException, InvalidKeyException, InvalidAlgorithmParameterException { + /* Skip 512-bit DH params in FIPS mode. FIPS 186-4 only allows + * 1024, 2048, and 3072-bit DH parameter generation */ + if (Fips.enabled) { + return; + } + /* create DH params */ AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.getInstance("DH"); paramGen.init(512); - AlgorithmParameters params = paramGen.generateParameters(); + + AlgorithmParameters params; + try { + params = paramGen.generateParameters(); + } + catch (RuntimeException e) { + /* 512-bit DH parameter generation may not be supported due to + * wolfSSL enforcing minimum parameter sizes. Skip test if + * generation fails. */ + if (e.getMessage() != null && e.getMessage().contains( + "Bad function argument")) { + System.out.println("\t512-bit DH parameter generation " + + "not supported, skipping test"); + return; + } + throw e; + } DHParameterSpec dhParams = (DHParameterSpec)params.getParameterSpec(DHParameterSpec.class); @@ -214,7 +239,7 @@ public void testDHKeyAgreement() assertArrayEquals(secretA, secretB); - /* now, try reusing the A object without calling init() again */ + /* Try reusing the A object without calling init() again */ KeyAgreement cKeyAgree = KeyAgreement.getInstance("DH", "wolfJCE"); KeyPair cPair = keyGen.generateKeyPair(); cKeyAgree.init(cPair.getPrivate()); @@ -235,11 +260,33 @@ public void testDHKeyAgreementWithUpdateArgument() InvalidAlgorithmParameterException, ShortBufferException { + /* Skip 512-bit DH params in FIPS mode. FIPS 186-4 only allows + * 1024, 2048, and 3072-bit DH parameter generation */ + if (Fips.enabled) { + return; + } + /* create DH params */ AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.getInstance("DH"); paramGen.init(512); - AlgorithmParameters params = paramGen.generateParameters(); + + AlgorithmParameters params; + try { + params = paramGen.generateParameters(); + } + catch (RuntimeException e) { + /* 512-bit DH parameter generation may not be supported due to + * wolfSSL enforcing minimum parameter sizes. Skip test if + * generation fails. */ + if (e.getMessage() != null && e.getMessage().contains( + "Bad function argument")) { + System.out.println("\t512-bit DH parameter generation " + + "not supported, skipping test"); + return; + } + throw e; + } DHParameterSpec dhParams = (DHParameterSpec)params.getParameterSpec(DHParameterSpec.class); @@ -291,11 +338,33 @@ public void testDHKeyAgreementInterop() InvalidParameterSpecException, InvalidKeyException, InvalidAlgorithmParameterException { + /* Skip 512-bit DH params in FIPS mode. FIPS 186-4 only allows + * 1024, 2048, and 3072-bit DH parameter generation */ + if (Fips.enabled) { + return; + } + /* create DH params */ AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.getInstance("DH"); paramGen.init(512); - AlgorithmParameters params = paramGen.generateParameters(); + + AlgorithmParameters params; + try { + params = paramGen.generateParameters(); + } + catch (RuntimeException e) { + /* 512-bit DH parameter generation may not be supported due to + * wolfSSL enforcing minimum parameter sizes. Skip test if + * generation fails. */ + if (e.getMessage() != null && e.getMessage().contains( + "Bad function argument")) { + System.out.println("\t512-bit DH parameter generation " + + "not supported, skipping test"); + return; + } + throw e; + } DHParameterSpec dhParams = (DHParameterSpec)params.getParameterSpec(DHParameterSpec.class); @@ -565,11 +634,36 @@ private void threadRunnerKeyAgreeTest(String algo) failures.set(0, 0); success.set(0, 0); - /* DH Tests */ - AlgorithmParameterGenerator paramGen = - AlgorithmParameterGenerator.getInstance("DH"); - paramGen.init(512); - final AlgorithmParameters params = paramGen.generateParameters(); + /* DH Tests - generate 512-bit params. Skip in FIPS mode since + * FIPS 186-4 only allows 1024, 2048, and 3072-bit DH parameter + * generation */ + final AlgorithmParameters params; + if (algo.equals("DH")) { + if (Fips.enabled) { + return; + } + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH"); + paramGen.init(512); + + try { + params = paramGen.generateParameters(); + } + catch (RuntimeException e) { + /* 512-bit DH parameter generation may not be supported due to + * wolfSSL enforcing minimum parameter sizes. Skip test if + * generation fails. */ + if (e.getMessage() != null && e.getMessage().contains( + "Bad function argument")) { + System.out.println("\t512-bit DH parameter generation " + + "not supported, skipping test"); + return; + } + throw e; + } + } else { + params = null; + } /* Do encrypt/decrypt and sign/verify in parallel across numThreads * threads, all operations should pass */ @@ -681,6 +775,319 @@ private void threadRunnerKeyAgreeTest(String algo) } } + @Test + public void testDHGenerateSecretKeyForDES() + throws NoSuchProviderException, NoSuchAlgorithmException, + InvalidParameterSpecException, InvalidKeyException, + InvalidAlgorithmParameterException { + + /* Skip 512-bit DH params in FIPS mode. FIPS 186-4 only allows + * 1024, 2048, and 3072-bit DH parameter generation */ + if (Fips.enabled) { + return; + } + + /* create DH params */ + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH"); + paramGen.init(512); + + AlgorithmParameters params; + try { + params = paramGen.generateParameters(); + } + catch (RuntimeException e) { + /* 512-bit DH parameter generation may not be supported due to + * wolfSSL enforcing minimum parameter sizes. Skip test if + * generation fails. */ + if (e.getMessage() != null && e.getMessage().contains( + "Bad function argument")) { + return; + } + throw e; + } + + DHParameterSpec dhParams = + (DHParameterSpec)params.getParameterSpec(DHParameterSpec.class); + + /* initialize key pair generator */ + KeyPairGenerator keyGen = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + keyGen.initialize(dhParams, secureRandom); + + KeyAgreement aKeyAgree = KeyAgreement.getInstance("DH", "wolfJCE"); + KeyAgreement bKeyAgree = KeyAgreement.getInstance("DH", "wolfJCE"); + + KeyPair aPair = keyGen.generateKeyPair(); + KeyPair bPair = keyGen.generateKeyPair(); + + aKeyAgree.init(aPair.getPrivate()); + bKeyAgree.init(bPair.getPrivate()); + + aKeyAgree.doPhase(bPair.getPublic(), true); + bKeyAgree.doPhase(aPair.getPublic(), true); + + /* Test generateSecret("DES") returns SecretKey, not DESKeySpec */ + SecretKey desKeyA = null; + SecretKey desKeyB = null; + try { + desKeyA = aKeyAgree.generateSecret("DES"); + assertNotNull(desKeyA); + assertTrue(desKeyA instanceof SecretKey); + assertEquals("DES", desKeyA.getAlgorithm()); + + /* Verify key can be used with Cipher */ + Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, desKeyA); + + } catch (ClassCastException e) { + fail("generateSecret(\"DES\") should return SecretKey, " + + "not DESKeySpec: " + e.getMessage()); + + } catch (Exception e) { + fail("Unexpected exception during DES key generation: " + + e.getMessage()); + } + + /* Test generateSecret("DESede") returns SecretKey */ + try { + /* bKeyAgree already has doPhase() completed, just generate + * secret with different algorithm */ + SecretKey desedeKey = bKeyAgree.generateSecret("DESede"); + assertNotNull(desedeKey); + assertTrue(desedeKey instanceof SecretKey); + assertEquals("DESede", desedeKey.getAlgorithm()); + + /* Verify key can be used with Cipher */ + Cipher cipher = + Cipher.getInstance("DESede/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, desedeKey); + + } catch (ClassCastException e) { + fail("generateSecret(\"DESede\") should return SecretKey, " + + "not DESedeKeySpec: " + e.getMessage()); + + } catch (Exception e) { + fail("Unexpected exception during DESede key generation: " + + e.getMessage()); + } + + /* Test generateSecret("AES") returns SecretKey with proper size */ + KeyAgreement cKeyAgree = KeyAgreement.getInstance("DH", "wolfJCE"); + KeyPair cPair = keyGen.generateKeyPair(); + cKeyAgree.init(cPair.getPrivate()); + cKeyAgree.doPhase(aPair.getPublic(), true); + + try { + SecretKey aesKey = cKeyAgree.generateSecret("AES"); + assertNotNull(aesKey); + assertTrue(aesKey instanceof SecretKey); + assertEquals("AES", aesKey.getAlgorithm()); + + /* AES key should be 16, 24, or 32 bytes */ + byte[] encoded = aesKey.getEncoded(); + assertTrue("AES key length should be 16, 24, or 32 bytes", + encoded.length == 16 || encoded.length == 24 || + encoded.length == 32); + + /* Verify key can be used with Cipher */ + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, aesKey); + + /* Test encryption/decryption */ + byte[] plaintext = "Test AES encryption".getBytes(); + byte[] ciphertext = cipher.doFinal(plaintext); + cipher.init(Cipher.DECRYPT_MODE, aesKey, + cipher.getParameters()); + byte[] decrypted = cipher.doFinal(ciphertext); + assertArrayEquals(plaintext, decrypted); + + } catch (Exception e) { + fail("Unexpected exception during AES key generation: " + + e.getMessage()); + } + } + + @Test + public void testECDHGenerateSecretKeyForDES() + throws NoSuchProviderException, NoSuchAlgorithmException, + InvalidParameterSpecException, InvalidKeyException, + InvalidAlgorithmParameterException { + + /* initialize key pair generator */ + KeyPairGenerator keyGen = + KeyPairGenerator.getInstance("EC", "wolfJCE"); + ECGenParameterSpec ecsp = new ECGenParameterSpec("secp256r1"); + keyGen.initialize(ecsp); + + KeyAgreement aKeyAgree = KeyAgreement.getInstance("ECDH", "wolfJCE"); + KeyAgreement bKeyAgree = KeyAgreement.getInstance("ECDH", "wolfJCE"); + + KeyPair aPair = keyGen.generateKeyPair(); + KeyPair bPair = keyGen.generateKeyPair(); + + aKeyAgree.init(aPair.getPrivate()); + bKeyAgree.init(bPair.getPrivate()); + + aKeyAgree.doPhase(bPair.getPublic(), true); + bKeyAgree.doPhase(aPair.getPublic(), true); + + /* Test generateSecret("DES") returns SecretKey, not DESKeySpec */ + try { + SecretKey desKey = aKeyAgree.generateSecret("DES"); + assertNotNull(desKey); + assertTrue(desKey instanceof SecretKey); + assertEquals("DES", desKey.getAlgorithm()); + + /* Verify key can be used with Cipher */ + Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, desKey); + + } catch (ClassCastException e) { + fail("generateSecret(\"DES\") should return SecretKey, " + + "not DESKeySpec: " + e.getMessage()); + + } catch (Exception e) { + fail("Unexpected exception during DES key generation: " + + e.getMessage()); + } + + /* Test generateSecret("DESede") returns SecretKey */ + try { + /* bKeyAgree already has doPhase() completed, just generate + * secret with different algorithm */ + SecretKey desedeKey = bKeyAgree.generateSecret("DESede"); + assertNotNull(desedeKey); + assertTrue(desedeKey instanceof SecretKey); + assertEquals("DESede", desedeKey.getAlgorithm()); + + /* Verify key can be used with Cipher */ + Cipher cipher = + Cipher.getInstance("DESede/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, desedeKey); + + } catch (ClassCastException e) { + fail("generateSecret(\"DESede\") should return SecretKey, " + + "not DESedeKeySpec: " + e.getMessage()); + + } catch (Exception e) { + fail("Unexpected exception during DESede key generation: " + + e.getMessage()); + } + + /* Test generateSecret("AES") returns SecretKey with proper size */ + KeyAgreement cKeyAgree = KeyAgreement.getInstance("ECDH", "wolfJCE"); + KeyPair cPair = keyGen.generateKeyPair(); + cKeyAgree.init(cPair.getPrivate()); + cKeyAgree.doPhase(aPair.getPublic(), true); + + try { + SecretKey aesKey = cKeyAgree.generateSecret("AES"); + assertNotNull(aesKey); + assertTrue(aesKey instanceof SecretKey); + assertEquals("AES", aesKey.getAlgorithm()); + + /* AES key should be 16, 24, or 32 bytes */ + byte[] encoded = aesKey.getEncoded(); + assertTrue("AES key length should be 16, 24, or 32 bytes", + encoded.length == 16 || encoded.length == 24 || + encoded.length == 32); + + /* Verify key can be used with Cipher */ + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, aesKey); + + /* Test encryption/decryption */ + byte[] plaintext = "Test AES encryption".getBytes(); + byte[] ciphertext = cipher.doFinal(plaintext); + cipher.init(Cipher.DECRYPT_MODE, aesKey, + cipher.getParameters()); + byte[] decrypted = cipher.doFinal(ciphertext); + assertArrayEquals(plaintext, decrypted); + + } catch (Exception e) { + fail("Unexpected exception during AES key generation: " + + e.getMessage()); + } + } + + @Test + public void testDHKeyAgreementPadding() + throws NoSuchProviderException, NoSuchAlgorithmException, + InvalidParameterSpecException, InvalidKeyException, + InvalidAlgorithmParameterException, + ShortBufferException { + + /* This test verifies that DH shared secrets are properly padded to + * the prime length when using generateSecret(byte[], int). This + * matches the behavior of SunJCE after Java 8 (JDK-7146728) and + * prevents regressions related to padding. Both generateSecret() + * methods pad to primeLen. */ + + /* Skip in FIPS mode. FIPS 186-4 only allows 1024, 2048, and + * 3072-bit DH parameter generation */ + if (Fips.enabled) { + return; + } + + /* Generate 2048-bit DH parameters */ + AlgorithmParameterGenerator paramGen = + AlgorithmParameterGenerator.getInstance("DH"); + paramGen.init(2048); + + AlgorithmParameters params = paramGen.generateParameters(); + DHParameterSpec dhParams = + (DHParameterSpec)params.getParameterSpec(DHParameterSpec.class); + + /* Prime length should be 256 bytes for 2048-bit DH */ + int primeLen = dhParams.getP().toByteArray().length; + if (dhParams.getP().toByteArray()[0] == 0x00) { + primeLen--; + } + + /* Initialize key pair generator */ + KeyPairGenerator keyGen = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + keyGen.initialize(dhParams, secureRandom); + + KeyAgreement alice = KeyAgreement.getInstance("DH", "wolfJCE"); + KeyAgreement bob = KeyAgreement.getInstance("DH", "wolfJCE"); + + /* Run multiple iterations to ensure consistent padding behavior */ + for (int i = 0; i < 100; i++) { + byte[] aliceSecret = new byte[primeLen]; + byte[] bobSecret = new byte[primeLen]; + + /* Fill buffers with different stale data to ensure padding + * overwrites any existing data */ + Arrays.fill(aliceSecret, (byte)'a'); + Arrays.fill(bobSecret, (byte)'b'); + + /* Generate new key pairs for this iteration */ + KeyPair aliceKeyPair = keyGen.generateKeyPair(); + KeyPair bobKeyPair = keyGen.generateKeyPair(); + + /* Perform key agreement */ + alice.init(aliceKeyPair.getPrivate()); + alice.doPhase(bobKeyPair.getPublic(), true); + int aliceLen = alice.generateSecret(aliceSecret, 0); + + bob.init(bobKeyPair.getPrivate()); + bob.doPhase(aliceKeyPair.getPublic(), true); + int bobLen = bob.generateSecret(bobSecret, 0); + + /* Both secrets should be exactly primeLen bytes (always padded) */ + assertEquals("Alice's secret length should equal prime length", + primeLen, aliceLen); + assertEquals("Bob's secret length should equal prime length", + primeLen, bobLen); + + /* Both secrets should be identical, including padding */ + assertArrayEquals("Alice and Bob should generate identical " + + "secrets at iteration " + i, aliceSecret, bobSecret); + } + } + @Test public void testThreadedKeyAgreement() throws InterruptedException, NoSuchAlgorithmException { diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyPairGeneratorTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyPairGeneratorTest.java index a1afe93e..48a349ff 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyPairGeneratorTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyPairGeneratorTest.java @@ -32,6 +32,8 @@ import java.util.ArrayList; import java.math.BigInteger; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.interfaces.DHPrivateKey; import javax.crypto.spec.DHParameterSpec; import java.security.Security; @@ -43,6 +45,7 @@ import java.security.PublicKey; import java.security.PrivateKey; import java.security.KeyFactory; +import java.security.InvalidParameterException; import java.security.InvalidAlgorithmParameterException; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAKeyGenParameterSpec; @@ -564,13 +567,54 @@ public void testKeyPairGeneratorDhInitWithKeySize() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { + /* Test that DH KeyPairGenerator supports initialize(int) with + * FFDHE standard key sizes (2048, 3072, 4096, 6144, 8192) */ + int[] ffdheKeySizes = { 2048, 3072, 4096, 6144, 8192 }; + + for (int keySize : ffdheKeySizes) { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + + try { + /* Initialize with FFDHE key size */ + kpg.initialize(keySize); + + /* Generate key pair to verify initialization worked */ + KeyPair kp = kpg.generateKeyPair(); + assertNotNull(kp); + assertNotNull(kp.getPublic()); + assertNotNull(kp.getPrivate()); + } + catch (InvalidParameterException e) { + /* FFDHE group may not be available in native wolfSSL. + * Skip if not available. */ + if (e.getMessage() != null && e.getMessage().contains( + "FFDHE " + keySize + "-bit group not available")) { + System.out.println("\tSkipping FFDHE " + keySize + + ": not compiled into native wolfSSL library"); + continue; + } + throw e; + } + catch (RuntimeException e) { + if (e.getMessage() != null && + e.getMessage().contains("group not available") || + e.getMessage().contains("Unsupported FFDHE group")) { + continue; + } + } + } + + /* Test that non-FFDHE sizes throw exception */ KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); try { kpg.initialize(512); - } catch (RuntimeException e) { - /* expected, users need to explicitly set DH params */ + fail("initialize(512) should throw InvalidParameterException " + + "for non-FFDHE key size"); + } catch (InvalidParameterException e) { + /* expected */ } } @@ -609,6 +653,78 @@ public void testKeyPairGeneratorDhMultipleKeyGen() assertNotNull(kp2); } + @Test + public void testKeyPairGeneratorDhDefaultKeySize() + throws NoSuchProviderException, NoSuchAlgorithmException { + + /* Test that DH KeyPairGenerator works with default parameters + * without explicit initialization. This matches SunJCE behavior. */ + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + + /* Generate key pair without calling initialize() first. + * Should use default FFDHE 3072-bit parameters. */ + KeyPair kp = null; + try { + kp = kpg.generateKeyPair(); + } + catch (RuntimeException e) { + /* Default FFDHE parameters may not be available in native + * wolfSSL. Skip test if not compiled in. */ + if (e.getMessage() != null && e.getMessage().contains( + "No DH parameters available")) { + return; + } + throw e; + } + + assertNotNull(kp); + assertNotNull(kp.getPublic()); + assertNotNull(kp.getPrivate()); + + /* Verify the generated key is DH */ + assertTrue(kp.getPublic() instanceof DHPublicKey); + assertTrue(kp.getPrivate() instanceof DHPrivateKey); + + DHPublicKey pubKey = (DHPublicKey) kp.getPublic(); + DHPrivateKey privKey = (DHPrivateKey) kp.getPrivate(); + + /* Verify parameters are present */ + assertNotNull(pubKey.getParams()); + assertNotNull(privKey.getParams()); + assertNotNull(pubKey.getParams().getP()); + assertNotNull(pubKey.getParams().getG()); + + /* Default should be FFDHE 3072-bit (to match SunJCE), but will + * fall back to FFDHE 2048 if 3072 is not compiled into wolfSSL. + * Check P is approximately 2048 or 3072 bits. + * BigInteger.bitLength() returns minimal bits needed, which may be + * less than the nominal size if there are leading zero bits. */ + int pBitLength = pubKey.getParams().getP().bitLength(); + assertTrue("Default DH prime should be approximately 2048 or " + + "3072 bits, got " + pBitLength, + (pBitLength >= 2016 && pBitLength <= 2048) || + (pBitLength >= 3008 && pBitLength <= 3072)); + + /* Verify keys use same parameters */ + assertEquals("Public and private keys should use same P", + pubKey.getParams().getP(), privKey.getParams().getP()); + assertEquals("Public and private keys should use same G", + pubKey.getParams().getG(), privKey.getParams().getG()); + + /* Generate another KeyPair to verify default params work repeatedly */ + KeyPair kp2 = kpg.generateKeyPair(); + assertNotNull(kp2); + + DHPublicKey pubKey2 = (DHPublicKey) kp2.getPublic(); + + /* Both key pairs should use same default parameters */ + assertEquals("Both key pairs should use same default P", + pubKey.getParams().getP(), pubKey2.getParams().getP()); + assertEquals("Both key pairs should use same default G", + pubKey.getParams().getG(), pubKey2.getParams().getG()); + } + @Test public void testKeyPairGeneratorRsaDefaultKeySize() throws NoSuchProviderException, NoSuchAlgorithmException { 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 acfc71b7..8f055b4e 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java @@ -44,7 +44,11 @@ WolfCryptKeyGeneratorTest.class, WolfCryptKeyPairGeneratorTest.class, WolfCryptECKeyFactoryTest.class, + WolfCryptDHKeyFactoryTest.class, WolfCryptPKIXCertPathValidatorTest.class, + WolfCryptAlgorithmParametersTest.class, + WolfCryptAlgorithmParameterGeneratorTest.class, + WolfCryptASN1UtilTest.class, WolfSSLKeyStoreTest.class, WolfCryptUtilTest.class })