diff --git a/jni/include/com_wolfssl_wolfcrypt_Dh.h b/jni/include/com_wolfssl_wolfcrypt_Dh.h index d006c38c..c56839d7 100644 --- a/jni/include/com_wolfssl_wolfcrypt_Dh.h +++ b/jni/include/com_wolfssl_wolfcrypt_Dh.h @@ -19,6 +19,14 @@ extern "C" { #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: dhMinSize + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Dh_dhMinSize + (JNIEnv *, jclass); + /* * Class: com_wolfssl_wolfcrypt_Dh * Method: mallocNativeStruct_internal diff --git a/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h b/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h index 6c28b3d7..1951f7cb 100644 --- a/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h +++ b/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h @@ -335,6 +335,14 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RsaKeyGenEna JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RsaPssEnabled (JNIEnv *, jclass); +/* + * Class: com_wolfssl_wolfcrypt_FeatureDetect + * Method: RsaPssLongSaltEnabled + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RsaPssLongSaltEnabled + (JNIEnv *, jclass); + /* * Class: com_wolfssl_wolfcrypt_FeatureDetect * Method: RsaOaepEnabled diff --git a/jni/jni_dh.c b/jni/jni_dh.c index c882c4e5..d541478f 100644 --- a/jni/jni_dh.c +++ b/jni/jni_dh.c @@ -41,6 +41,18 @@ #define RNG WC_RNG #endif +/* Some FIPS versions don't have DH_MIN_SIZE defined. Values match those + * used by wolfSSL settings.h. */ +#ifndef DH_MIN_SIZE + #ifdef HAVE_FIPS + #define DH_MIN_SIZE 2048 + #elif defined(WOLFSSL_MIN_DHKEY_BITS) + #define DH_MIN_SIZE WOLFSSL_MIN_DHKEY_BITS + #else + #define DH_MIN_SIZE 1024 + #endif +#endif + /* Some FIPS versions don't have DH_MAX_SIZE defined */ #ifndef DH_MAX_SIZE #ifdef USE_FAST_MATH @@ -60,6 +72,19 @@ #endif #endif +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Dh_dhMinSize + (JNIEnv* env, jclass jcl) +{ + (void)env; + (void)jcl; + +#if !defined(NO_DH) + return (jint)DH_MIN_SIZE; +#else + return 0; +#endif +} + JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_Dh_mallocNativeStruct_1internal( JNIEnv* env, jobject this) { diff --git a/jni/jni_feature_detect.c b/jni/jni_feature_detect.c index b00fdcf8..2920627b 100644 --- a/jni/jni_feature_detect.c +++ b/jni/jni_feature_detect.c @@ -522,6 +522,18 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RsaPssEnable #endif } +JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RsaPssLongSaltEnabled + (JNIEnv* env, jclass jcl) +{ + (void)env; + (void)jcl; +#if !defined(NO_RSA) && defined(WC_RSA_PSS) && defined(WOLFSSL_PSS_LONG_SALT) + return JNI_TRUE; +#else + return JNI_FALSE; +#endif +} + JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RsaOaepEnabled (JNIEnv* env, jclass jcl) { diff --git a/jni/jni_wolfcrypt.c b/jni/jni_wolfcrypt.c index 14bef3bf..a45852ee 100644 --- a/jni/jni_wolfcrypt.c +++ b/jni/jni_wolfcrypt.c @@ -42,6 +42,17 @@ /* #define WOLFCRYPT_JNI_DEBUG_ON */ #include +/* Max PEM input size for the single-block PEM to DER conversion functions. + * Ample room for real cert/key, but bounds upper memory use. */ +#define WC_JNI_MAX_PEM_SIZE (1024 * 1024) + +/* Force-zero a buffer holding sensitive material */ +#if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && !defined(WOLFSSL_NO_FORCE_ZERO) + #define WC_JNI_FORCE_ZERO(p, len) wc_ForceZero((p), (len)) +#else + #define WC_JNI_FORCE_ZERO(p, len) XMEMSET((p), 0, (len)) +#endif + JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_getWC_1HASH_1TYPE_1NONE (JNIEnv* env, jclass class) { @@ -365,6 +376,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_wcKeyPemToDer byte* pem = NULL; byte* der = NULL; const char* password = NULL; + jboolean pwIsCopy = JNI_FALSE; jbyteArray derArr = NULL; (void)jcl; @@ -377,20 +389,16 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_wcKeyPemToDer } if (ret == 0) { - pem = (byte*)(*env)->GetByteArrayElements(env, pemArr, NULL); pemSz = (*env)->GetArrayLength(env, pemArr); - if (pem == NULL || pemSz <= 0) { + if (pemSz <= 0 || pemSz > WC_JNI_MAX_PEM_SIZE) { ret = BAD_FUNC_ARG; } } - /* Get password if provided */ if (ret == 0) { - if (passwordStr != NULL) { - password = (*env)->GetStringUTFChars(env, passwordStr, NULL); - if (password == NULL) { - ret = MEMORY_E; - } + pem = (byte*)(*env)->GetByteArrayElements(env, pemArr, NULL); + if (pem == NULL) { + ret = BAD_FUNC_ARG; } } @@ -400,10 +408,22 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_wcKeyPemToDer if (der == NULL) { ret = MEMORY_E; } + else { + XMEMSET(der, 0, pemSz); + } + } + + /* Get password if provided */ + if (ret == 0) { + if (passwordStr != NULL) { + password = (*env)->GetStringUTFChars(env, passwordStr, &pwIsCopy); + if (password == NULL) { + ret = MEMORY_E; + } + } } if (ret == 0) { - XMEMSET(der, 0, pemSz); ret = wc_KeyPemToDer(pem, pemSz, der, pemSz, password); if (ret > 0) { derSz = ret; @@ -431,15 +451,14 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_wcKeyPemToDer (*env)->ReleaseByteArrayElements(env, pemArr, (jbyte*)pem, JNI_ABORT); } if (password != NULL) { + /* Only clear when JNI handed back a private copy */ + if (pwIsCopy == JNI_TRUE) { + WC_JNI_FORCE_ZERO((void*)password, XSTRLEN(password)); + } (*env)->ReleaseStringUTFChars(env, passwordStr, password); } if (der != NULL) { - #if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && \ - !defined(WOLFSSL_NO_FORCE_ZERO) - wc_ForceZero(der, pemSz); - #else - XMEMSET(der, 0, pemSz); - #endif + WC_JNI_FORCE_ZERO(der, pemSz); XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); } if (ret != 0) { @@ -479,9 +498,15 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_wcCertPemToDer } if (ret == 0) { - pem = (byte*)(*env)->GetByteArrayElements(env, pemArr, NULL); pemSz = (*env)->GetArrayLength(env, pemArr); - if (pem == NULL || pemSz <= 0) { + if (pemSz <= 0 || pemSz > WC_JNI_MAX_PEM_SIZE) { + ret = BAD_FUNC_ARG; + } + } + + if (ret == 0) { + pem = (byte*)(*env)->GetByteArrayElements(env, pemArr, NULL); + if (pem == NULL) { ret = BAD_FUNC_ARG; } } @@ -561,9 +586,15 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_wcPubKeyPemToD } if (ret == 0) { - pem = (byte*)(*env)->GetByteArrayElements(env, pemArr, NULL); pemSz = (*env)->GetArrayLength(env, pemArr); - if (pem == NULL || pemSz <= 0) { + if (pemSz <= 0 || pemSz > WC_JNI_MAX_PEM_SIZE) { + ret = BAD_FUNC_ARG; + } + } + + if (ret == 0) { + pem = (byte*)(*env)->GetByteArrayElements(env, pemArr, NULL); + if (pem == NULL) { ret = BAD_FUNC_ARG; } } diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java index 57253f38..6bc9e6c9 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java @@ -793,6 +793,33 @@ public class WolfCryptCipher extends CipherSpi { } } + /** + * Convert an AEAD tag length from bits to bytes. + * + * Rejects lengths that are not a whole number of bytes, since the byte + * count is what reaches native wolfSSL and a non-multiple of 8 would be + * silently truncated. Native validates which sizes the mode supports. + * + * @param modeName mode label used in the error message + * @param tagBits requested tag length in bits + * + * @return tag length in bytes + * + * @throws InvalidAlgorithmParameterException if tagBits is not a + * positive multiple of 8 + */ + private static int tagLenToBytes(String modeName, int tagBits) + throws InvalidAlgorithmParameterException { + + if (tagBits <= 0 || (tagBits % 8) != 0) { + throw new InvalidAlgorithmParameterException( + modeName + " tag length must be a positive multiple of " + + "8 bits, got " + tagBits); + } + + return (tagBits / 8); + } + private void wolfCryptSetIV(AlgorithmParameterSpec spec, SecureRandom random) throws InvalidAlgorithmParameterException { @@ -853,13 +880,7 @@ public class WolfCryptCipher extends CipherSpi { } this.iv = gcmSpec.getIV().clone(); - - /* store tag length as bytes */ - if (gcmSpec.getTLen() == 0) { - throw new InvalidAlgorithmParameterException( - "Tag length cannot be zero"); - } - this.gcmTagLen = (gcmSpec.getTLen() / 8); + this.gcmTagLen = tagLenToBytes("AES-GCM", gcmSpec.getTLen()); } else if (cipherMode == CipherMode.WC_CCM) { /* @@ -891,13 +912,7 @@ public class WolfCryptCipher extends CipherSpi { } this.iv = ccmSpec.getIV().clone(); - - /* store tag length as bytes */ - if (ccmSpec.getTLen() == 0) { - throw new InvalidAlgorithmParameterException( - "Tag length cannot be zero"); - } - this.gcmTagLen = (ccmSpec.getTLen() / 8); + this.gcmTagLen = tagLenToBytes("AES-CCM", ccmSpec.getTLen()); } else { if (!(spec instanceof IvParameterSpec)) { diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java index 0caff1f5..5b261a90 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java @@ -198,6 +198,10 @@ public class WolfCryptDHPrivateKey implements DHPrivateKey, Destroyable { } pLen = WolfCryptASN1Util.getDERLength(derData, idx); idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + if (pLen < 0 || pLen > derData.length - idx) { + throw new IllegalArgumentException( + "Invalid PKCS#8: p length exceeds buffer"); + } pBytes = new byte[pLen]; System.arraycopy(derData, idx, pBytes, 0, pLen); p = new BigInteger(1, pBytes); @@ -210,6 +214,10 @@ public class WolfCryptDHPrivateKey implements DHPrivateKey, Destroyable { } gLen = WolfCryptASN1Util.getDERLength(derData, idx); idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + if (gLen < 0 || gLen > derData.length - idx) { + throw new IllegalArgumentException( + "Invalid PKCS#8: g length exceeds buffer"); + } gBytes = new byte[gLen]; System.arraycopy(derData, idx, gBytes, 0, gLen); g = new BigInteger(1, gBytes); @@ -230,6 +238,10 @@ public class WolfCryptDHPrivateKey implements DHPrivateKey, Destroyable { } privLen = WolfCryptASN1Util.getDERLength(derData, idx); idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + if (privLen < 0 || privLen > derData.length - idx) { + throw new IllegalArgumentException( + "Invalid PKCS#8: private value length exceeds buffer"); + } privBytes = new byte[privLen]; System.arraycopy(derData, idx, privBytes, 0, privLen); privateVal = new BigInteger(1, privBytes); @@ -382,6 +394,10 @@ public class WolfCryptDHPrivateKey implements DHPrivateKey, Destroyable { } pLen = WolfCryptASN1Util.getDERLength(this.encoded, idx); idx += WolfCryptASN1Util.getDERLengthSize(this.encoded, idx); + if (pLen < 0 || pLen > this.encoded.length - idx) { + throw new IllegalStateException( + "Invalid PKCS#8: p length exceeds buffer"); + } pBytes = new byte[pLen]; System.arraycopy(this.encoded, idx, pBytes, 0, pLen); p = new BigInteger(1, pBytes); @@ -394,6 +410,10 @@ public class WolfCryptDHPrivateKey implements DHPrivateKey, Destroyable { } gLen = WolfCryptASN1Util.getDERLength(this.encoded, idx); idx += WolfCryptASN1Util.getDERLengthSize(this.encoded, idx); + if (gLen < 0 || gLen > this.encoded.length - idx) { + throw new IllegalStateException( + "Invalid PKCS#8: g length exceeds buffer"); + } gBytes = new byte[gLen]; System.arraycopy(this.encoded, idx, gBytes, 0, gLen); g = new BigInteger(1, gBytes); @@ -486,6 +506,10 @@ public class WolfCryptDHPrivateKey implements DHPrivateKey, Destroyable { } privLen = WolfCryptASN1Util.getDERLength(this.encoded, idx); idx += WolfCryptASN1Util.getDERLengthSize(this.encoded, idx); + if (privLen < 0 || privLen > this.encoded.length - idx) { + throw new IllegalStateException( + "Invalid PKCS#8: private value length exceeds buffer"); + } privBytes = new byte[privLen]; System.arraycopy(this.encoded, idx, privBytes, 0, privLen); privateVal = new BigInteger(1, privBytes); diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java index dc38bef4..35d97a46 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java @@ -189,6 +189,10 @@ public class WolfCryptDHPublicKey implements DHPublicKey, Destroyable { } pLen = WolfCryptASN1Util.getDERLength(derData, idx); idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + if (pLen < 0 || pLen > derData.length - idx) { + throw new IllegalArgumentException( + "Invalid X.509: p length exceeds buffer"); + } pBytes = new byte[pLen]; System.arraycopy(derData, idx, pBytes, 0, pLen); p = new BigInteger(1, pBytes); @@ -201,6 +205,10 @@ public class WolfCryptDHPublicKey implements DHPublicKey, Destroyable { } gLen = WolfCryptASN1Util.getDERLength(derData, idx); idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + if (gLen < 0 || gLen > derData.length - idx) { + throw new IllegalArgumentException( + "Invalid X.509: g length exceeds buffer"); + } gBytes = new byte[gLen]; System.arraycopy(derData, idx, gBytes, 0, gLen); g = new BigInteger(1, gBytes); @@ -224,6 +232,10 @@ public class WolfCryptDHPublicKey implements DHPublicKey, Destroyable { } pubLen = WolfCryptASN1Util.getDERLength(derData, idx); idx += WolfCryptASN1Util.getDERLengthSize(derData, idx); + if (pubLen < 0 || pubLen > derData.length - idx) { + throw new IllegalArgumentException( + "Invalid X.509: public value length exceeds buffer"); + } pubBytes = new byte[pubLen]; System.arraycopy(derData, idx, pubBytes, 0, pubLen); publicVal = new BigInteger(1, pubBytes); @@ -374,6 +386,10 @@ public class WolfCryptDHPublicKey implements DHPublicKey, Destroyable { } pLen = WolfCryptASN1Util.getDERLength(this.encoded, idx); idx += WolfCryptASN1Util.getDERLengthSize(this.encoded, idx); + if (pLen < 0 || pLen > this.encoded.length - idx) { + throw new IllegalStateException( + "Invalid X.509: p length exceeds buffer"); + } pBytes = new byte[pLen]; System.arraycopy(this.encoded, idx, pBytes, 0, pLen); p = new BigInteger(1, pBytes); @@ -386,6 +402,10 @@ public class WolfCryptDHPublicKey implements DHPublicKey, Destroyable { } gLen = WolfCryptASN1Util.getDERLength(this.encoded, idx); idx += WolfCryptASN1Util.getDERLengthSize(this.encoded, idx); + if (gLen < 0 || gLen > this.encoded.length - idx) { + throw new IllegalStateException( + "Invalid X.509: g length exceeds buffer"); + } gBytes = new byte[gLen]; System.arraycopy(this.encoded, idx, gBytes, 0, gLen); g = new BigInteger(1, gBytes); @@ -472,6 +492,10 @@ public class WolfCryptDHPublicKey implements DHPublicKey, Destroyable { } pubLen = WolfCryptASN1Util.getDERLength(this.encoded, idx); idx += WolfCryptASN1Util.getDERLengthSize(this.encoded, idx); + if (pubLen < 0 || pubLen > this.encoded.length - idx) { + throw new IllegalStateException( + "Invalid X.509: public value length exceeds buffer"); + } pubBytes = new byte[pubLen]; System.arraycopy(this.encoded, idx, pubBytes, 0, pubLen); publicVal = new BigInteger(1, pubBytes); diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java index ddedf586..e8aaea00 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyPairGenerator.java @@ -476,14 +476,31 @@ public class WolfCryptKeyPairGenerator extends KeyPairGeneratorSpi { } DHParameterSpec dhSpec = (DHParameterSpec)params; - this.dhP = dhSpec.getP().toByteArray(); - this.dhG = dhSpec.getG().toByteArray(); + BigInteger dhSpecP = dhSpec.getP(); + BigInteger dhSpecG = dhSpec.getG(); - if (dhP == null || dhG == null) { + if (dhSpecP == null || dhSpecG == null) { throw new InvalidAlgorithmParameterException( "Invalid parameters, either p or g is null"); } + if (dhSpecP.signum() <= 0 || dhSpecG.signum() <= 0) { + throw new InvalidAlgorithmParameterException( + "DH parameters p and g must be positive"); + } + + /* Reject primes the native library would refuse, so the + * failure throws here instead of from generateKeyPair() */ + int dhPrimeBits = dhSpecP.bitLength(); + if (dhPrimeBits < Dh.DH_MIN_SIZE) { + throw new InvalidAlgorithmParameterException( + "DH prime size must be at least " + Dh.DH_MIN_SIZE + + " bits, got " + dhPrimeBits); + } + + this.dhP = dhSpecP.toByteArray(); + this.dhG = dhSpecG.toByteArray(); + log("init with spec, prime len: " + this.dhP.length); break; diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java index 5b0374f2..2a806a9b 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java @@ -280,8 +280,8 @@ public class WolfCryptSignature extends SignatureSpi { "Unsupported signature algorithm digest type"); } - /* Initialize PSS parameters if PSS padding */ - if (ptype == PaddingType.WC_RSA_PSS) { + /* Set default PSS parameters, only if caller has not already */ + if (ptype == PaddingType.WC_RSA_PSS && this.pssParams == null) { String digestAlg = digestTypeToJavaName(dtype); MGF1ParameterSpec mgf1Spec = getMGF1SpecForDigest(digestAlg); int saltLen = this.digestSz; /* Use actual hash length */ @@ -1246,6 +1246,24 @@ public class WolfCryptSignature extends SignatureSpi { "Only MGF1 supported, got " + mgfAlg); } + /* Validate spec type and MGF1 inner digest */ + AlgorithmParameterSpec mgfParams = pss.getMGFParameters(); + if (mgfParams != null) { + /* Different spec type silently falls back to the default digest */ + if (!(mgfParams instanceof MGF1ParameterSpec)) { + throw new InvalidAlgorithmParameterException( + "MGF1 parameters must be of type MGF1ParameterSpec, got " + + mgfParams.getClass().getName()); + } + + String mgfDigest = + ((MGF1ParameterSpec)mgfParams).getDigestAlgorithm(); + if (!isDigestSupported(mgfDigest)) { + throw new InvalidAlgorithmParameterException( + "MGF1 digest not supported: " + mgfDigest); + } + } + /* Validate salt length is reasonable */ int saltLen = pss.getSaltLength(); if (saltLen < -2) { diff --git a/src/main/java/com/wolfssl/wolfcrypt/AesCcm.java b/src/main/java/com/wolfssl/wolfcrypt/AesCcm.java index 5f674bd6..64453200 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/AesCcm.java +++ b/src/main/java/com/wolfssl/wolfcrypt/AesCcm.java @@ -109,8 +109,8 @@ public class AesCcm extends NativeStruct { (state != WolfCryptState.RELEASED)) { synchronized (pointerLock) { wc_AesFree(); + super.releaseNativeStruct(); } - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java b/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java index e3da954f..d6cb4b3c 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java +++ b/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java @@ -109,8 +109,8 @@ public class AesGcm extends NativeStruct { (state != WolfCryptState.RELEASED)) { synchronized (pointerLock) { wc_AesFree(); + super.releaseNativeStruct(); } - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/Dh.java b/src/main/java/com/wolfssl/wolfcrypt/Dh.java index 00719d80..6553028a 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Dh.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Dh.java @@ -38,6 +38,13 @@ public class Dh extends NativeStruct { /** Lock around object state */ protected final Object stateLock = new Object(); + /** + * Minimum DH prime size in bits accepted by the native wolfSSL build. + * Tracks native DH_MIN_SIZE, which is build configurable through + * WOLFSSL_MIN_DHKEY_BITS. + */ + public static final int DH_MIN_SIZE = Dh.dhMinSize(); + /* Named DH group constants (FFDHE from RFC 7919) */ /** FFDHE 2048-bit group */ public static final int WC_FFDHE_2048 = 256; @@ -93,16 +100,17 @@ public class Dh extends NativeStruct { synchronized (pointerLock) { wc_FreeDhKey(); + super.releaseNativeStruct(); } setPrivateKey(new byte[0]); setPublicKey(new byte[0]); - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } } + private static native int dhMinSize(); private native long mallocNativeStruct_internal() throws OutOfMemoryError; private native void wc_InitDhKey(); private native void wc_FreeDhKey(); diff --git a/src/main/java/com/wolfssl/wolfcrypt/Ecc.java b/src/main/java/com/wolfssl/wolfcrypt/Ecc.java index b62978a7..dc38a6f6 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Ecc.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Ecc.java @@ -90,6 +90,7 @@ public class Ecc extends NativeStruct { synchronized (pointerLock) { wc_ecc_free(); + super.releaseNativeStruct(); } synchronized (rngLock) { @@ -100,7 +101,6 @@ public class Ecc extends NativeStruct { } } - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/Ed25519.java b/src/main/java/com/wolfssl/wolfcrypt/Ed25519.java index 336523f3..e439680d 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Ed25519.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Ed25519.java @@ -63,8 +63,8 @@ public class Ed25519 extends NativeStruct { synchronized (pointerLock) { wc_ed25519_free(); + super.releaseNativeStruct(); } - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java b/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java index 8223e32b..654beb05 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java +++ b/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java @@ -325,6 +325,14 @@ public class FeatureDetect { */ public static native boolean RsaPssEnabled(); + /** + * Tests if RSA-PSS salt lengths longer than the digest are compiled into + * the native wolfSSL library (WOLFSSL_PSS_LONG_SALT). + * + * @return true if enabled, otherwise false if not compiled in. + */ + public static native boolean RsaPssLongSaltEnabled(); + /** * Tests if RSA-OAEP is compiled into the native wolfSSL library. * diff --git a/src/main/java/com/wolfssl/wolfcrypt/Lms.java b/src/main/java/com/wolfssl/wolfcrypt/Lms.java index 261bb250..b65a5c6c 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Lms.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Lms.java @@ -95,8 +95,8 @@ public class Lms extends NativeStruct { synchronized (pointerLock) { wc_LmsKey_free(); + super.releaseNativeStruct(); } - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/MlDsa.java b/src/main/java/com/wolfssl/wolfcrypt/MlDsa.java index 4ccaff5b..021ace23 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/MlDsa.java +++ b/src/main/java/com/wolfssl/wolfcrypt/MlDsa.java @@ -144,8 +144,8 @@ public class MlDsa extends NativeStruct { synchronized (pointerLock) { wc_dilithium_free(); + super.releaseNativeStruct(); } - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/MlKem.java b/src/main/java/com/wolfssl/wolfcrypt/MlKem.java index 47367ff1..521ac5c9 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/MlKem.java +++ b/src/main/java/com/wolfssl/wolfcrypt/MlKem.java @@ -124,8 +124,8 @@ public class MlKem extends NativeStruct { synchronized (pointerLock) { wc_mlkem_free(); + super.releaseNativeStruct(); } - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/Rsa.java b/src/main/java/com/wolfssl/wolfcrypt/Rsa.java index 01d54c9e..eb746137 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Rsa.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Rsa.java @@ -250,8 +250,8 @@ public class Rsa extends NativeStruct { synchronized (pointerLock) { wc_FreeRsaKey(); + super.releaseNativeStruct(); } - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/SlhDsa.java b/src/main/java/com/wolfssl/wolfcrypt/SlhDsa.java index 12fe3a28..22468ff5 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/SlhDsa.java +++ b/src/main/java/com/wolfssl/wolfcrypt/SlhDsa.java @@ -189,8 +189,8 @@ public class SlhDsa extends NativeStruct { synchronized (pointerLock) { wc_SlhDsaKey_free(); + super.releaseNativeStruct(); } - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java b/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java index 774af02b..05aa70b8 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java +++ b/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java @@ -344,9 +344,10 @@ public class WolfCrypt extends WolfObject { * * @return DER-encoded private key as byte array * - * @throws WolfCryptException if conversion fails, native operation - * encounters an error, or native ASN/PEM support is not - * compiled in (NO_ASN or WOLFSSL_NO_PEM defined) + * @throws WolfCryptException if conversion fails, input is larger than + * the 1 MB maximum PEM size, native operation encounters an + * error, or native ASN/PEM support is not compiled in + * (NO_ASN or WOLFSSL_NO_PEM defined) */ public static byte[] keyPemToDer(byte[] pem, String password) throws WolfCryptException { @@ -367,9 +368,10 @@ public class WolfCrypt extends WolfObject { * * @return DER-encoded certificate as byte array * - * @throws WolfCryptException if conversion fails, native operation - * encounters an error, or native ASN/PEM support is not - * compiled in (NO_ASN or WOLFSSL_NO_PEM defined) + * @throws WolfCryptException if conversion fails, input is larger than + * the 1 MB maximum PEM size, native operation encounters an + * error, or native ASN/PEM support is not compiled in + * (NO_ASN or WOLFSSL_NO_PEM defined) */ public static byte[] certPemToDer(byte[] pem) throws WolfCryptException { @@ -389,9 +391,10 @@ public class WolfCrypt extends WolfObject { * * @return DER-encoded public key as byte array * - * @throws WolfCryptException if conversion fails, native operation - * encounters an error, or native ASN/PEM support is not - * compiled in (NO_ASN or WOLFSSL_NO_PEM defined) + * @throws WolfCryptException if conversion fails, input is larger than + * the 1 MB maximum PEM size, native operation encounters an + * error, or native ASN/PEM support is not compiled in + * (NO_ASN or WOLFSSL_NO_PEM defined) */ public static byte[] pubKeyPemToDer(byte[] pem) throws WolfCryptException { diff --git a/src/main/java/com/wolfssl/wolfcrypt/Xmss.java b/src/main/java/com/wolfssl/wolfcrypt/Xmss.java index 8eed9373..68716794 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Xmss.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Xmss.java @@ -82,8 +82,8 @@ public class Xmss extends NativeStruct { synchronized (pointerLock) { wc_XmssKey_free(); + super.releaseNativeStruct(); } - super.releaseNativeStruct(); state = WolfCryptState.RELEASED; } } diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java index a226f340..67b72989 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java @@ -84,6 +84,7 @@ import com.wolfssl.wolfcrypt.FeatureDetect; import com.wolfssl.wolfcrypt.Aes; import com.wolfssl.wolfcrypt.Fips; import com.wolfssl.provider.jce.WolfCryptProvider; +import java.security.GeneralSecurityException; import com.wolfssl.wolfcrypt.WolfCryptException; import com.wolfssl.wolfcrypt.test.TimedTestWatcher; @@ -5772,6 +5773,127 @@ public class WolfCryptCipherTest { } } + @Test + public void testAesGcmTagLengthValidation() throws Exception { + + if (!enabledJCEAlgos.contains("AES/GCM/NoPadding")) { + return; + } + + byte[] key = new byte[16]; + byte[] iv = new byte[12]; + SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); + + int[] truncatingTagBits = { 4, 100, 129 }; + for (int tagBits : truncatingTagBits) { + Cipher cipher = + Cipher.getInstance("AES/GCM/NoPadding", jceProvider); + try { + cipher.init(Cipher.ENCRYPT_MODE, keySpec, + new GCMParameterSpec(tagBits, iv)); + fail("Should reject GCM tag length " + tagBits + " bits"); + } catch (InvalidAlgorithmParameterException e) { + assertTrue("Error should mention tag length", + e.getMessage().contains("tag length")); + } + } + + /* Whole byte counts, so rejected below the JCE layer rather than at + * init. Only sizes over the AES block size are rejected on every + * build, minimum tag size enforcement varies by configuration. */ + int[] unsupportedTagBits = { 256 }; + for (int tagBits : unsupportedTagBits) { + Cipher cipher = + Cipher.getInstance("AES/GCM/NoPadding", jceProvider); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, + new GCMParameterSpec(tagBits, iv)); + try { + cipher.doFinal("test".getBytes()); + fail("Should reject GCM tag length " + tagBits + " bits"); + } catch (WolfCryptException | GeneralSecurityException e) { + /* expected */ + } + } + + byte[] plaintext = "GCM tag length test".getBytes(); + int[] goodTagBits = { 96, 104, 112, 120, 128 }; + for (int tagBits : goodTagBits) { + secureRandom.nextBytes(iv); + Cipher enc = + Cipher.getInstance("AES/GCM/NoPadding", jceProvider); + enc.init(Cipher.ENCRYPT_MODE, keySpec, + new GCMParameterSpec(tagBits, iv)); + byte[] ct = enc.doFinal(plaintext); + + Cipher dec = + Cipher.getInstance("AES/GCM/NoPadding", jceProvider); + dec.init(Cipher.DECRYPT_MODE, keySpec, + new GCMParameterSpec(tagBits, iv)); + assertArrayEquals(plaintext, dec.doFinal(ct)); + } + } + + @Test + public void testAesCcmTagLengthValidation() throws Exception { + + if (!enabledJCEAlgos.contains("AES/CCM/NoPadding")) { + return; + } + + byte[] key = new byte[16]; + byte[] nonce = new byte[12]; + SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); + + int[] truncatingTagBits = { 4, 100, 129 }; + for (int tagBits : truncatingTagBits) { + Cipher cipher = + Cipher.getInstance("AES/CCM/NoPadding", jceProvider); + try { + cipher.init(Cipher.ENCRYPT_MODE, keySpec, + new GCMParameterSpec(tagBits, nonce)); + fail("Should reject CCM tag length " + tagBits + " bits"); + } catch (InvalidAlgorithmParameterException e) { + assertTrue("Error should mention tag length", + e.getMessage().contains("tag length")); + } + } + + /* Whole byte counts, so rejected below the JCE layer rather than at + * init. Only sizes over the AES block size are rejected on every + * build, the RFC 3610 set is not enforced in all FIPS bundles. */ + int[] unsupportedTagBits = { 256 }; + for (int tagBits : unsupportedTagBits) { + Cipher cipher = + Cipher.getInstance("AES/CCM/NoPadding", jceProvider); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, + new GCMParameterSpec(tagBits, nonce)); + try { + cipher.doFinal("test".getBytes()); + fail("Should reject CCM tag length " + tagBits + " bits"); + } catch (WolfCryptException | GeneralSecurityException e) { + /* expected */ + } + } + + /* 32-bit tags are valid for CCM, unlike GCM */ + byte[] plaintext = "CCM tag length test".getBytes(); + int[] goodTagBits = { 32, 64, 96, 128 }; + for (int tagBits : goodTagBits) { + secureRandom.nextBytes(nonce); + Cipher enc = + Cipher.getInstance("AES/CCM/NoPadding", jceProvider); + enc.init(Cipher.ENCRYPT_MODE, keySpec, + new GCMParameterSpec(tagBits, nonce)); + byte[] ct = enc.doFinal(plaintext); + + Cipher dec = + Cipher.getInstance("AES/CCM/NoPadding", jceProvider); + dec.init(Cipher.DECRYPT_MODE, keySpec, + new GCMParameterSpec(tagBits, nonce)); + assertArrayEquals(plaintext, dec.doFinal(ct)); + } + } + /** * Test IV consistency across encryption/decryption cycles. * This test prevents regression of the issue where getIV() would return diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java index 5e7ad484..11195565 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java @@ -51,6 +51,7 @@ import org.junit.BeforeClass; import org.junit.Test; import com.wolfssl.provider.jce.WolfCryptProvider; +import java.lang.reflect.Field; import com.wolfssl.provider.jce.WolfCryptDHPublicKey; import com.wolfssl.wolfcrypt.FeatureDetect; import com.wolfssl.wolfcrypt.test.TimedTestWatcher; @@ -490,6 +491,81 @@ public class WolfCryptDHKeyFactoryTest { } } + @Test + public void testPKCS8RejectsOversizedDERFieldLength() throws Exception { + + Assume.assumeTrue(FeatureDetect.DhEnabled()); + + KeyFactory kf = KeyFactory.getInstance("DH", "wolfJCE"); + + /* PKCS#8 that parses up to the p INTEGER, whose length is a 4-byte + * long-form 0x7FFFFFFF. Guard must reject it as a clean + * InvalidKeySpecException instead. */ + byte[] malicious = new byte[] { + (byte)0x30, (byte)0x30, + (byte)0x02, (byte)0x01, (byte)0x00, + (byte)0x30, (byte)0x03, + (byte)0x06, (byte)0x01, (byte)0x2A, + (byte)0x30, (byte)0x09, + (byte)0x02, + (byte)0x84, (byte)0x7F, (byte)0xFF, (byte)0xFF, (byte)0xFF + }; + + try { + kf.generatePrivate(new PKCS8EncodedKeySpec(malicious)); + fail("Should reject PKCS#8 with oversized DER field length"); + + } catch (InvalidKeySpecException e) { + /* Expected */ + } + + /* Positive test: a well-formed key still parses */ + if (enabledKeySizes.contains(2048)) { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + byte[] valid = kpg.generateKeyPair().getPrivate().getEncoded(); + assertNotNull(kf.generatePrivate(new PKCS8EncodedKeySpec(valid))); + } + } + + @Test + public void testX509RejectsOversizedDERFieldLength() throws Exception { + + Assume.assumeTrue(FeatureDetect.DhEnabled()); + + KeyFactory kf = KeyFactory.getInstance("DH", "wolfJCE"); + + /* X.509 SPKI that parses up to the p INTEGER, whose length is a + * 4-byte long-form 0x7FFFFFFF. Guard must reject it as a clean + * InvalidKeySpecException instead. */ + byte[] malicious = new byte[] { + (byte)0x30, (byte)0x30, + (byte)0x30, (byte)0x03, + (byte)0x06, (byte)0x01, (byte)0x2A, + (byte)0x30, (byte)0x09, + (byte)0x02, + (byte)0x84, (byte)0x7F, (byte)0xFF, (byte)0xFF, (byte)0xFF + }; + + try { + kf.generatePublic(new X509EncodedKeySpec(malicious)); + fail("Should reject X.509 with oversized DER field length"); + + } catch (InvalidKeySpecException e) { + /* Expected */ + } + + /* Positive test: a well-formed key still parses */ + if (enabledKeySizes.contains(2048)) { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + byte[] valid = kpg.generateKeyPair().getPublic().getEncoded(); + assertNotNull(kf.generatePublic(new X509EncodedKeySpec(valid))); + } + } + @Test public void testDHPublicKeySpecRangeValidation() throws Exception { @@ -994,5 +1070,95 @@ public class WolfCryptDHKeyFactoryTest { assertEquals("Public key p should match", originalPub.getParams().getP(), roundTripPubDH.getParams().getP()); } -} + /** + * Replace the cached DER of a key and clear its lazily-extracted fields, + * so the next getter re-parses the supplied bytes. Mirrors the state a + * key is left in after deserialization, where transient caches are null + * but the encoded form is populated. + */ + private static void resetEncoded(Object key, byte[] der, + String... cacheFields) throws Exception { + + Field enc = key.getClass().getDeclaredField("encoded"); + enc.setAccessible(true); + enc.set(key, der); + + for (String name : cacheFields) { + Field f = key.getClass().getDeclaredField(name); + f.setAccessible(true); + f.set(key, null); + } + } + + /* X.509 SPKI whose p INTEGER carries a 4-byte long-form 0x7FFFFFFF + * length, used to drive the lazy extraction guards */ + private static final byte[] oversizedSpki = new byte[] { + (byte)0x30, (byte)0x30, + (byte)0x30, (byte)0x03, + (byte)0x06, (byte)0x01, (byte)0x2A, + (byte)0x30, (byte)0x09, + (byte)0x02, + (byte)0x84, (byte)0x7F, (byte)0xFF, (byte)0xFF, (byte)0xFF + }; + + @Test + public void testPublicKeyLazyExtractRejectsOversizedLength() + throws Exception { + + Assume.assumeTrue(FeatureDetect.DhEnabled()); + Assume.assumeTrue(enabledKeySizes.contains(2048)); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + DHPublicKey pub = (DHPublicKey)kpg.generateKeyPair().getPublic(); + + resetEncoded(pub, oversizedSpki, "paramSpec", "publicValue"); + + try { + pub.getParams(); + fail("getParams() should reject an oversized DER length"); + } catch (IllegalStateException e) { + /* expected */ + } + + try { + pub.getY(); + fail("getY() should reject an oversized DER length"); + } catch (IllegalStateException e) { + /* expected */ + } + } + + @Test + public void testPrivateKeyLazyExtractRejectsOversizedLength() + throws Exception { + + Assume.assumeTrue(FeatureDetect.DhEnabled()); + Assume.assumeTrue(enabledKeySizes.contains(2048)); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + DHPrivateKey priv = (DHPrivateKey)kpg.generateKeyPair().getPrivate(); + + /* PKCS#8 variant, adds the version INTEGER ahead of the params */ + byte[] oversizedPkcs8 = new byte[] { + (byte)0x30, (byte)0x30, + (byte)0x02, (byte)0x01, (byte)0x00, + (byte)0x30, (byte)0x03, + (byte)0x06, (byte)0x01, (byte)0x2A, + (byte)0x30, (byte)0x09, + (byte)0x02, + (byte)0x84, (byte)0x7F, (byte)0xFF, (byte)0xFF, (byte)0xFF + }; + + resetEncoded(priv, oversizedPkcs8, "paramSpec", "privateValue"); + + try { + priv.getParams(); + fail("getParams() should reject an oversized DER length"); + } catch (IllegalStateException e) { + /* expected */ + } + } +} 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 a3789e72..c4f1b7f5 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyPairGeneratorTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptKeyPairGeneratorTest.java @@ -67,6 +67,7 @@ import java.security.interfaces.ECPrivateKey; import java.security.spec.ECParameterSpec; import com.wolfssl.wolfcrypt.Rsa; +import com.wolfssl.wolfcrypt.Dh; import com.wolfssl.wolfcrypt.Ecc; import com.wolfssl.wolfcrypt.Fips; import com.wolfssl.wolfcrypt.FeatureDetect; @@ -598,7 +599,7 @@ public class WolfCryptKeyPairGeneratorTest { KeyPairGenerator.getInstance("DH", "wolfJCE"); DHParameterSpec spec = new DHParameterSpec( - new BigInteger(prime), + new BigInteger(1, prime), new BigInteger(base), testDHKeySizes[i]); @@ -609,6 +610,103 @@ public class WolfCryptKeyPairGeneratorTest { } } + @Test + public void testKeyPairGeneratorDhRejectsSmallPrimeSpec() + throws NoSuchProviderException, NoSuchAlgorithmException, + InvalidAlgorithmParameterException { + + /* skip test if DH is not compiled in native wolfSSL */ + if (!FeatureDetect.DhEnabled()) { + return; + } + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + + /* 255-bit prime (Curve25519 field prime), below the 512-bit min, + * must be rejected before key generation */ + DHParameterSpec weakSpec = new DHParameterSpec( + new BigInteger("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFED", 16), + new BigInteger(base)); + + try { + kpg.initialize(weakSpec); + fail("initialize(DHParameterSpec) should reject a prime " + + "below the native minimum"); + } catch (InvalidAlgorithmParameterException e) { + /* expected */ + } + + /* Positive check: 2048-bit prime spec should be accepted */ + DHParameterSpec goodSpec = new DHParameterSpec( + new BigInteger(1, prime), new BigInteger(base)); + kpg.initialize(goodSpec); + assertNotNull(kpg.generateKeyPair()); + } + + @Test + public void testKeyPairGeneratorDhRejectsNonPositiveParams() + throws NoSuchProviderException, NoSuchAlgorithmException, + InvalidAlgorithmParameterException { + + /* skip test if DH is not compiled in native wolfSSL */ + if (!FeatureDetect.DhEnabled()) { + return; + } + + BigInteger goodP = new BigInteger(1, prime); + BigInteger goodG = new BigInteger(base); + + /* A negative p can still clear the minimum bit length check */ + BigInteger[][] bad = new BigInteger[][] { + { goodP.negate(), goodG }, + { goodP, goodG.negate() }, + { BigInteger.ZERO, goodG }, + { goodP, BigInteger.ZERO } + }; + + for (BigInteger[] pg : bad) { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("DH", "wolfJCE"); + try { + kpg.initialize(new DHParameterSpec(pg[0], pg[1])); + fail("initialize() should reject non-positive DH params"); + } catch (InvalidAlgorithmParameterException e) { + /* expected */ + } + } + } + + @Test + public void testKeyPairGeneratorDhRejectedSpecLeavesStateIntact() + throws NoSuchProviderException, NoSuchAlgorithmException, + InvalidAlgorithmParameterException { + + /* skip test if DH is not compiled in native wolfSSL */ + if (!FeatureDetect.DhEnabled()) { + return; + } + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "wolfJCE"); + kpg.initialize(2048); + + DHParameterSpec weakSpec = new DHParameterSpec( + BigInteger.ONE.shiftLeft(255).setBit(0), new BigInteger(base)); + + try { + kpg.initialize(weakSpec); + fail("initialize(DHParameterSpec) should reject a prime " + + "below the native minimum"); + } catch (InvalidAlgorithmParameterException e) { + /* expected */ + } + + /* The rejected spec must not have replaced the 2048-bit params */ + DHPrivateKey priv = (DHPrivateKey)kpg.generateKeyPair().getPrivate(); + assertTrue("Rejected spec must not weaken generator state", + priv.getParams().getP().bitLength() >= Dh.DH_MIN_SIZE); + } + @Test public void testKeyPairGeneratorDhInitWithKeySize() throws NoSuchProviderException, NoSuchAlgorithmException, @@ -684,7 +782,7 @@ public class WolfCryptKeyPairGeneratorTest { KeyPairGenerator.getInstance("DH", "wolfJCE"); DHParameterSpec spec = new DHParameterSpec( - new BigInteger(prime), new BigInteger(base), 512); + new BigInteger(1, prime), new BigInteger(base), 512); kpg.initialize(spec); kpg.initialize(spec); @@ -704,7 +802,7 @@ public class WolfCryptKeyPairGeneratorTest { KeyPairGenerator.getInstance("DH", "wolfJCE"); DHParameterSpec spec = new DHParameterSpec( - new BigInteger(prime), new BigInteger(base), 512); + new BigInteger(1, prime), new BigInteger(base), 512); kpg.initialize(spec); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSignatureTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSignatureTest.java index 9bbf77f8..3df3f516 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSignatureTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSignatureTest.java @@ -1737,9 +1737,7 @@ public class WolfCryptSignatureTest { @Test public void testRsaPssEdgeCases() - throws NoSuchProviderException, NoSuchAlgorithmException, - SignatureException, InvalidKeyException, - InvalidAlgorithmParameterException { + throws Exception { if (!enabledAlgos.contains("RSASSA-PSS") || !com.wolfssl.wolfcrypt.FeatureDetect.RsaPssEnabled()) { @@ -1819,9 +1817,13 @@ public class WolfCryptSignatureTest { } private void testRsaPssMaxSaltLengths() - throws NoSuchProviderException, NoSuchAlgorithmException, - SignatureException, InvalidKeyException, - InvalidAlgorithmParameterException { + throws Exception { + + if (!FeatureDetect.RsaPssLongSaltEnabled()) { + System.out.println("\tSkipping max salt lengths, " + + "WOLFSSL_PSS_LONG_SALT not compiled in"); + return; + } String message = "Testing maximum salt lengths"; byte[] messageBytes = message.getBytes(); @@ -2093,10 +2095,10 @@ public class WolfCryptSignatureTest { } @Test - public void testRsaPssMultipleUpdates() + public void testRsaPssPreservesCallerParameters() throws NoSuchProviderException, NoSuchAlgorithmException, SignatureException, InvalidKeyException, - InvalidAlgorithmParameterException { + InvalidAlgorithmParameterException, Exception { if (!enabledAlgos.contains("RSASSA-PSS") || !com.wolfssl.wolfcrypt.FeatureDetect.RsaPssEnabled()) { @@ -2104,6 +2106,122 @@ public class WolfCryptSignatureTest { return; } + byte[] data = "PSS parameter test".getBytes(); + + /* RFC 4055 style parameters, MGF1 inner digest and salt length + * both differ from the message digest */ + PSSParameterSpec spec = new PSSParameterSpec( + "SHA-256", "MGF1", MGF1ParameterSpec.SHA1, 20, 1); + + Signature signer = Signature.getInstance("RSASSA-PSS", "wolfJCE"); + signer.setParameter(spec); + + /* Parameters must survive the internal digest initialization */ + AlgorithmParameters algParams = signer.getParameters(); + assertNotNull(algParams); + PSSParameterSpec effective = + algParams.getParameterSpec(PSSParameterSpec.class); + + assertEquals("Message digest should be preserved", + "SHA-256", effective.getDigestAlgorithm()); + assertEquals("Salt length should be preserved", + 20, effective.getSaltLength()); + assertTrue("MGF parameters should be MGF1ParameterSpec", + effective.getMGFParameters() instanceof MGF1ParameterSpec); + assertEquals("MGF1 inner digest should be preserved", "SHA-1", + ((MGF1ParameterSpec)effective.getMGFParameters()) + .getDigestAlgorithm()); + + signer.initSign(rsaPair.getPrivate()); + signer.update(data); + byte[] signature = signer.sign(); + + Signature verifier = Signature.getInstance("RSASSA-PSS", "wolfJCE"); + verifier.setParameter(spec); + verifier.initVerify(rsaPair.getPublic()); + verifier.update(data); + assertTrue("wolfJCE should verify its own PSS signature", + verifier.verify(signature)); + + /* Signature must also verify under another provider using the same + * parameters, proving the requested values were actually used */ + try { + Signature interop = + Signature.getInstance("RSASSA-PSS", "SunRsaSign"); + interop.setParameter(spec); + interop.initVerify(rsaPair.getPublic()); + interop.update(data); + assertTrue("SunRsaSign should verify with requested params", + interop.verify(signature)); + + } catch (NoSuchProviderException | NoSuchAlgorithmException e) { + /* SunRsaSign RSASSA-PSS not available, skip interop check */ + } + } + + @Test + public void testRsaPssRejectsNonMgf1ParameterSpec() + throws NoSuchProviderException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException { + + if (!enabledAlgos.contains("RSASSA-PSS") || + !com.wolfssl.wolfcrypt.FeatureDetect.RsaPssEnabled()) { + /* Skip if RSA-PSS not enabled at JCE or native level */ + return; + } + + Signature signer = Signature.getInstance("RSASSA-PSS", "wolfJCE"); + + try { + signer.setParameter(new PSSParameterSpec("SHA-256", "MGF1", + new ECGenParameterSpec("secp256r1"), 32, 1)); + fail("Should reject MGF parameters that are not MGF1ParameterSpec"); + + } catch (InvalidAlgorithmParameterException e) { + /* expected */ + } + } + + @Test + public void testRsaPssRejectsUnsupportedMgf1Digest() + throws NoSuchProviderException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException { + + if (!enabledAlgos.contains("RSASSA-PSS") || + !com.wolfssl.wolfcrypt.FeatureDetect.RsaPssEnabled()) { + /* Skip if RSA-PSS not enabled at JCE or native level */ + return; + } + + Signature signer = Signature.getInstance("RSASSA-PSS", "wolfJCE"); + + try { + signer.setParameter(new PSSParameterSpec( + "SHA-256", "MGF1", new MGF1ParameterSpec("MD5"), 32, 1)); + fail("Should reject unsupported MGF1 inner digest"); + + } catch (InvalidAlgorithmParameterException e) { + /* expected */ + } + } + + @Test + public void testRsaPssMultipleUpdates() + throws Exception { + + if (!enabledAlgos.contains("RSASSA-PSS") || + !com.wolfssl.wolfcrypt.FeatureDetect.RsaPssEnabled()) { + /* Skip if RSA-PSS not enabled at JCE or native level */ + return; + } + + /* Uses the maximum salt length for each digest */ + if (!FeatureDetect.RsaPssLongSaltEnabled()) { + System.out.println( + "\tSkipping, WOLFSSL_PSS_LONG_SALT not compiled in"); + return; + } + /* Create test data */ byte[] data = new byte[100]; for (int i = 0; i < data.length; i++) { diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTest.java index f344fe45..d732b888 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTest.java @@ -32,13 +32,16 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.security.KeyPairGenerator; import java.util.Arrays; +import java.util.Base64; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import com.wolfssl.wolfcrypt.WolfCrypt; +import com.wolfssl.wolfcrypt.WolfCryptError; import com.wolfssl.wolfcrypt.WolfCryptException; /** @@ -539,6 +542,83 @@ public class WolfCryptTest { WolfCrypt.certPemToDer(invalidPem); } + /** + * Pad a PEM buffer out to the requested total size with newlines. The + * PEM block stays parseable, so only the input size limit can reject it. + */ + private static byte[] padPem(byte[] pem, int totalSz) { + + if (totalSz < pem.length) { + throw new IllegalArgumentException( + "totalSz must be >= PEM length"); + } + + byte[] padded = new byte[totalSz]; + + Arrays.fill(padded, (byte)'\n'); + System.arraycopy(pem, 0, padded, 0, pem.length); + + return padded; + } + + /* Input over the native size limit should be rejected before any large + * native buffer is allocated, even though PEM block itself is valid. */ + @Test + public void testCertPemToDerOversizedInput() throws Exception { + + if (!fileExists(clientCertPem)) { + System.out.println("Skipping: test file not found"); + return; + } + + /* One byte past the inclusive limit is the first rejected size */ + int[] tooBig = { 1024 * 1024 + 1, 2 * 1024 * 1024 }; + for (int size : tooBig) { + try { + WolfCrypt.certPemToDer( + padPem(readFile(clientCertPem), size)); + fail("Should reject PEM input of " + size + " bytes"); + + } catch (WolfCryptException e) { + /* expected */ + } + } + } + + @Test + public void testKeyPemToDerOversizedInput() throws Exception { + + if (!fileExists(clientKeyPem)) { + System.out.println("Skipping: test file not found"); + return; + } + + try { + WolfCrypt.keyPemToDer( + padPem(readFile(clientKeyPem), 2 * 1024 * 1024), null); + fail("Should reject PEM input larger than the size limit"); + + } catch (WolfCryptException e) { + /* expected */ + } + } + + @Test + public void testCertPemToDerAtSizeLimitStillWorks() throws Exception { + + if (!fileExists(clientCertPem) || !fileExists(clientCertDer)) { + System.out.println("Skipping: test files not found"); + return; + } + + /* Exactly at the limit, which is inclusive, must still convert */ + byte[] der = WolfCrypt.certPemToDer( + padPem(readFile(clientCertPem), 1024 * 1024)); + + assertArrayEquals("DER output should match expected", + readFile(clientCertDer), der); + } + @Test public void testCertPemToDerOutputSmallerThanInput() throws Exception { @@ -576,6 +656,91 @@ public class WolfCryptTest { (der[0] & 0xFF) == 0x30); } + /* AES-256 encrypted PKCS#8 RSA-2048 private key, password below. + * This is a test key only, so safe to ship here with password. */ + private static final String encKeyPassword = "wolfsslpassword"; + private static final String encKeyPem = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + + "MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQHgyFAxVE0s7p+tCz\n" + + "Ay/aHQICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEECOvnAgfjJoG6IIy\n" + + "nLVImwoEggTQBSNWX1HfO8gbANTzqFTQIj3A1VFMYgx7ddVXfr++W79iR6R+KiSP\n" + + "eYrWfOveMXbnjdzgQRUuN0dytG61ygulXaYYsDH4OAJW3iMne7zGoLXQ4yCM4xhU\n" + + "+bvju6I7Q6vpg1/gRqUEDeOA78PjBbvGKA7Als1xdr2/IzP2U389svDacKZV3pC6\n" + + "6af4+6HrRIJxxfVYzXHk4J1E0bQNv9qYm+T026aG5W9ucOFbXL/ZQK5s3WygpiKJ\n" + + "ERRkam+KX4kVFkj58+3Z6LO9N8FVohQuoLODxzaMsOqWEUPoIaNZTYZHB6rFOidN\n" + + "ncGg2TOYo5I6E++aaol+JU+UqJ56hSAow2Wot3OM9Lq+XPkZJxsHLywM0yjv2ayg\n" + + "BKwsiEDhDfSIvbXVo7d4LaGfIJRa02/I9KyWBW9u73rfadehqcF2DQcEcY/Vo9fu\n" + + "iZ0gqFiKRJtlODaSgsqXiZZAEhJrB/TkXsINWCCKrQXEc2ZrUlTYlNiF8+9u/jwG\n" + + "HokqdkFbJ+RxX4hyLbWXlfxOm90B98mQWz6l3SSgTxfX3Tty45oWpAZUzB7xD796\n" + + "Ginjjlan9kHiNC0zNcmD4K17tsk7zowYNb07hn11GYLx33qX3D64kdfzaKGbmaDx\n" + + "qxBKysNM7LP9tNt+tAnboPx3da9TlCjc9n2AZl6n1S3jYqev3H09NUGPQFWp6zgl\n" + + "wFsompYmJ+loNioBWcws6Rjnb8LRXlK8aSeGJmFSH/Z9RlpCy9Yp/sg6ky56garS\n" + + "IyRmmIDrU/cyjVOFVONKS5Epx46ioUbI7k7/EMkMRAan59JTrE4ss/aQvCGMdxZn\n" + + "kfqSdCusHWgK4IO5kSg++bNpPIv654WUE3xSTqvm5zg17ClI1II0mTHHxvuBNFUw\n" + + "hgoAbkCUtWZ1Y/WV6qOpJs0bWC16rLsopebAAoajIGDEDdVl6AeDQ7rFiIP9AD04\n" + + "szr8MBPvuo3dQB0hAwxK3VJ33fyBXVtEyz+suFPBDubhB4knQNXKj6895UWdje1p\n" + + "BksIQ53bN3Bsfk4QC+Rqoo68+gXrAD0tPt00i8Bcb0GUhwyJcrvS8lvQ+tMfSBff\n" + + "EEHS1i5YrPXizFa2/mjm44wPetGr023UkMUI0IYEI0SVlnIAybbUJRt6DSCa9wwz\n" + + "/nakascm7U9gkSZfhxtvb2D/iN0zH/Jd49U+trLAcXDIetJd/SodUXzF12qwFfJY\n" + + "S0I5ZCW/4opND7fHb9oE56Zt7U9+Ijs0qllkppo5L8kjgSDuiRkAn4FHG9viuo9p\n" + + "H+9bWvn4Os2Mo7dGMuQf8TDaNkjtQdrz14pYrQcp6fRt87610dcx2q1Z8dGFtg3D\n" + + "FXMxYSFITIAI3jzpjETpSXkIpKk3+6KKWhTahwng6BJXm4FLuv5/7QQrau8dIsjC\n" + + "2BkclkgPSTs2VEL0OzvgQtWOUIgy3jysyuwcJf3dHUg7J5wlixzh+eLvbpsx7zsB\n" + + "Qx0a7DAgn0yi9LiRTqMUsK8ELXgtBJlaUlgClhjzDqMlSKDj+0tuWkuEgkR7nvNj\n" + + "d8WDJJJXRCZtqDp4bTACEpPJMOB8pytOSg45LCubmvZ26YG5FdPCbIN94vRiohzw\n" + + "wbCMgU3cacMLX9x8gTvJptRmAeedp97wCoqdHGiebkFgAbNMjNHFrio=\n" + + "-----END ENCRYPTED PRIVATE KEY-----\n"; + + /** + * Decrypt the embedded key, or return null when the native build lacks + * PKCS#8 encryption, PBKDF2, or AES-256-CBC support. + */ + private static byte[] decryptEncKeyPem() throws Exception { + try { + return WolfCrypt.keyPemToDer( + encKeyPem.getBytes("UTF-8"), encKeyPassword); + + } catch (WolfCryptException e) { + if (e.getError() == WolfCryptError.NOT_COMPILED_IN) { + return null; + } + throw e; + } + } + + @Test + public void testKeyPemToDerWithPassword() throws Exception { + + byte[] der = decryptEncKeyPem(); + if (der == null) { + System.out.println("Skipping: encrypted PKCS#8 not compiled in"); + return; + } + + assertEquals("DER should start with SEQUENCE tag", + 0x30, der[0] & 0xFF); + } + + @Test + public void testKeyPemToDerWrongPassword() throws Exception { + + /* Confirm the correct password works first, otherwise the negative + * case below could pass for the wrong reason */ + if (decryptEncKeyPem() == null) { + System.out.println("Skipping: encrypted PKCS#8 not compiled in"); + return; + } + + try { + WolfCrypt.keyPemToDer( + encKeyPem.getBytes("UTF-8"), "wrongpassword"); + fail("Should reject an incorrect password"); + + } catch (WolfCryptException e) { + /* expected */ + } + } + @Test(expected = WolfCryptException.class) public void testPubKeyPemToDerNullInput() throws Exception { WolfCrypt.pubKeyPemToDer(null); @@ -592,6 +757,45 @@ public class WolfCryptTest { WolfCrypt.pubKeyPemToDer(invalidPem); } + /** + * Build a SubjectPublicKeyInfo PEM. No standalone public key PEM ships + * under examples/certs, so one is generated here. + */ + private static byte[] generatePubKeyPem() throws Exception { + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + byte[] spki = kpg.generateKeyPair().getPublic().getEncoded(); + + StringBuilder sb = new StringBuilder("-----BEGIN PUBLIC KEY-----\n"); + String b64 = Base64.getEncoder().encodeToString(spki); + for (int i = 0; i < b64.length(); i += 64) { + sb.append(b64, i, Math.min(i + 64, b64.length())).append('\n'); + } + sb.append("-----END PUBLIC KEY-----\n"); + + return sb.toString().getBytes("UTF-8"); + } + + @Test(expected = WolfCryptException.class) + public void testPubKeyPemToDerOversizedInput() throws Exception { + WolfCrypt.pubKeyPemToDer( + padPem(generatePubKeyPem(), 2 * 1024 * 1024)); + } + + @Test + public void testPubKeyPemToDerAtSizeLimitStillWorks() throws Exception { + + byte[] pem = generatePubKeyPem(); + byte[] expectedDer = WolfCrypt.pubKeyPemToDer(pem); + + /* Exactly at the limit, which is inclusive, must still convert */ + byte[] der = WolfCrypt.pubKeyPemToDer(padPem(pem, 1024 * 1024)); + + assertArrayEquals("DER output should match unpadded conversion", + expectedDer, der); + } + @Test public void testKeyDerHasValidAsn1Structure() throws Exception {