diff --git a/jni/include/com_wolfssl_wolfcrypt_AesGcm.h b/jni/include/com_wolfssl_wolfcrypt_AesGcm.h index 4ef846b4..23e14c60 100644 --- a/jni/include/com_wolfssl_wolfcrypt_AesGcm.h +++ b/jni/include/com_wolfssl_wolfcrypt_AesGcm.h @@ -57,6 +57,33 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncrypt JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmDecrypt (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray, jbyteArray); +/* + * Class: com_wolfssl_wolfcrypt_AesGcm + * Method: wc_AesGcmEncryptInitStreaming + * Signature: ([B)V + */ +JNIEXPORT void JNICALL +Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncryptInitStreaming( + JNIEnv *, jobject, jbyteArray); + +/* + * Class: com_wolfssl_wolfcrypt_AesGcm + * Method: wc_AesGcmEncryptUpdateStreaming + * Signature: ([B[B)[B + */ +JNIEXPORT jbyteArray JNICALL +Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncryptUpdateStreaming( + JNIEnv *, jobject, jbyteArray, jbyteArray); + +/* + * Class: com_wolfssl_wolfcrypt_AesGcm + * Method: wc_AesGcmEncryptFinalStreaming + * Signature: (I)[B + */ +JNIEXPORT jbyteArray JNICALL +Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncryptFinalStreaming( + JNIEnv *, jobject, jint); + #ifdef __cplusplus } #endif diff --git a/jni/jni_aesgcm.c b/jni/jni_aesgcm.c index f14cac2b..23633dd6 100644 --- a/jni/jni_aesgcm.c +++ b/jni/jni_aesgcm.c @@ -415,3 +415,229 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmDecrypt #endif } +/* + * Initialize AES-GCM streaming encryption. + * Key must already be loaded via wc_AesGcmSetKey. This call sets the IV + * and prepares internal streaming state (WOLFSSL_AESGCM_STREAM). + */ +JNIEXPORT void JNICALL +Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncryptInitStreaming( + JNIEnv* env, jobject this, jbyteArray ivArr) +{ +#if !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AESGCM_STREAM) + int ret = 0; + Aes* aes = NULL; + const byte* iv = NULL; + word32 ivSz = 0; + + aes = (Aes*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + return; + } + + if (ivArr != NULL) { + iv = (const byte*)(*env)->GetByteArrayElements(env, ivArr, NULL); + ivSz = (*env)->GetArrayLength(env, ivArr); + } + + if (iv == NULL || ivSz == 0) { + ret = BAD_FUNC_ARG; + } + + /* + * Pass NULL key (key already loaded via wc_AesGcmSetKey). + * wc_AesGcmEncryptInit only sets key when key != NULL. + */ + if (ret == 0) { + ret = wc_AesGcmEncryptInit(aes, NULL, 0, iv, ivSz); + } + + if (ivArr != NULL) { + (*env)->ReleaseByteArrayElements(env, ivArr, (jbyte*)iv, JNI_ABORT); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } + + LogStr("wc_AesGcmEncryptInit(aes = %p, ivSz = %d)\n", aes, ivSz); +#else + (void)this; + (void)ivArr; + throwNotCompiledInException(env); +#endif +} + +/* + * Streaming AES-GCM encrypt update: encrypt plaintext and/or process AAD. + * inputArr may be NULL or empty (AAD-only call). + * authInArr may be NULL (no AAD for this call). + * Returns a jbyteArray of length inputArr.length containing ciphertext. + */ +JNIEXPORT jbyteArray JNICALL +Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncryptUpdateStreaming( + JNIEnv* env, jobject this, jbyteArray inputArr, + jbyteArray authInArr) +{ +#if !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AESGCM_STREAM) + int ret = 0; + Aes* aes = NULL; + const byte* in = NULL; + const byte* authIn = NULL; + word32 inLen = 0; + word32 authInSz = 0; + byte* out = NULL; + jbyteArray outArr = NULL; + + aes = (Aes*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + return NULL; + } + + if (inputArr != NULL) { + in = (const byte*)(*env)->GetByteArrayElements(env, inputArr, NULL); + inLen = (*env)->GetArrayLength(env, inputArr); + if ((inLen > 0) && (in == NULL)) { + ret = BAD_FUNC_ARG; + } + } + if ((ret == 0) && (authInArr != NULL)) { + authIn = (const byte*)(*env)->GetByteArrayElements(env, + authInArr, NULL); + authInSz = (*env)->GetArrayLength(env, authInArr); + if ((authInSz > 0) && (authIn == NULL)) { + ret = BAD_FUNC_ARG; + } + } + + if ((ret == 0) && (inLen > 0)) { + out = (byte*)XMALLOC(inLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (out == NULL) { + ret = MEMORY_E; + } + else { + XMEMSET(out, 0, inLen); + } + } + + if (ret == 0) { + ret = wc_AesGcmEncryptUpdate(aes, out, in, inLen, authIn, authInSz); + } + + if (ret == 0) { + outArr = (*env)->NewByteArray(env, (jsize)inLen); + if (outArr == NULL) { + ret = MEMORY_E; + } + else if (inLen > 0) { + (*env)->SetByteArrayRegion(env, outArr, 0, (jsize)inLen, + (jbyte*)out); + if ((*env)->ExceptionOccurred(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + (*env)->DeleteLocalRef(env, outArr); + outArr = NULL; + ret = -1; + } + } + } + + if (inputArr != NULL) { + (*env)->ReleaseByteArrayElements(env, inputArr, (jbyte*)in, JNI_ABORT); + } + if (authInArr != NULL) { + (*env)->ReleaseByteArrayElements(env, authInArr, (jbyte*)authIn, + JNI_ABORT); + } + if (out != NULL) { + XFREE(out, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + LogStr("wc_AesGcmEncryptUpdate(aes = %p, inLen = %d, authInSz = %d)\n", + aes, inLen, authInSz); + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + + return outArr; + +#else + (void)this; + (void)inputArr; + (void)authInArr; + throwNotCompiledInException(env); + return NULL; +#endif +} + +/* + * Finalize AES-GCM streaming encryption and generate authentication tag. + * Returns a jbyteArray of length tagLen containing the authentication tag. + */ +JNIEXPORT jbyteArray JNICALL +Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncryptFinalStreaming( + JNIEnv* env, jobject this, jint tagLen) +{ +#if !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AESGCM_STREAM) + int ret = 0; + Aes* aes = NULL; + byte* tag = NULL; + jbyteArray tagArr = NULL; + + aes = (Aes*) getNativeStruct(env, this); + if ((*env)->ExceptionOccurred(env)) { + return NULL; + } + + if (tagLen <= 0 || tagLen > AES_BLOCK_SIZE) { + throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG); + return NULL; + } + + tag = (byte*)XMALLOC((word32)tagLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (tag == NULL) { + throwWolfCryptExceptionFromError(env, MEMORY_E); + return NULL; + } + XMEMSET(tag, 0, (word32)tagLen); + + ret = wc_AesGcmEncryptFinal(aes, tag, (word32)tagLen); + + if (ret == 0) { + tagArr = (*env)->NewByteArray(env, tagLen); + if (tagArr == NULL) { + ret = MEMORY_E; + } + else { + (*env)->SetByteArrayRegion(env, tagArr, 0, tagLen, (jbyte*)tag); + if ((*env)->ExceptionOccurred(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + (*env)->DeleteLocalRef(env, tagArr); + tagArr = NULL; + ret = -1; + } + } + } + + XFREE(tag, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + LogStr("wc_AesGcmEncryptFinal(aes = %p, tagLen = %d)\n", aes, tagLen); + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + return NULL; + } + + return tagArr; + +#else + (void)this; + (void)tagLen; + throwNotCompiledInException(env); + return NULL; +#endif +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java index be44bcae..6772b066 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java @@ -58,6 +58,7 @@ import com.wolfssl.wolfcrypt.Aes; import com.wolfssl.wolfcrypt.AesEcb; +import com.wolfssl.wolfcrypt.FeatureDetect; import com.wolfssl.wolfcrypt.AesCtr; import com.wolfssl.wolfcrypt.AesOfb; import com.wolfssl.wolfcrypt.AesGcm; @@ -135,6 +136,7 @@ enum RsaKeyType { /* RSA-OAEP parameters */ private int oaepHashType = 0; private int oaepMgf = 0; + private OAEPParameterSpec oaepSpec = null; /* for debug logging */ private String algString; @@ -145,6 +147,12 @@ enum RsaKeyType { private AlgorithmParameterSpec storedSpec = null; private byte[] iv = null; + /* Maximum plaintext size for AEAD modes (GCM/CCM). + * GCM counter is 32 bits; NIST SP 800-38D allows up to + * (2^32 - 2) * 16 bytes. We limit to Integer.MAX_VALUE to + * prevent counter wrap and OOM from unbounded buffering. */ + private static final int AEAD_MAX_PLAINTEXT = Integer.MAX_VALUE; + /* AES-GCM/CCM tag length (bytes), default to 128 bits */ private int gcmTagLen = 16; @@ -154,9 +162,19 @@ enum RsaKeyType { /* Has update/final been called yet, gates setting of AAD for GCM */ private boolean operationStarted = false; - /* Has this Cipher been inintialized? */ + /* Has this Cipher been initialized? */ private boolean cipherInitialized = false; + /* Has engineDoFinal() been called without re-init? Prevents IV reuse. */ + private boolean finalized = false; + + /* Streaming GCM encrypt state (WOLFSSL_AESGCM_STREAM) */ + private boolean gcmStreamingActive = false; + private boolean gcmAadPassed = false; + private long gcmBytesEncrypted = 0L; + /* NIST SP 800-38D: max plaintext = (2^32 - 2) * 16 bytes per session */ + private static final long GCM_MAX_PLAINTEXT_BYTES = 0xFFFFFFFEL * 16L; + /* buffered data from update calls */ private byte[] buffered = new byte[0]; @@ -201,6 +219,8 @@ private WolfCryptCipher(CipherType type, CipherMode mode, private void initOaepParams() { this.oaepHashType = WolfCrypt.WC_HASH_TYPE_SHA256; this.oaepMgf = Rsa.WC_MGF1SHA1; + this.oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1", + MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT); } /** @@ -211,6 +231,8 @@ private void initOaepParams() { private void initOaepParamsSha1() { this.oaepHashType = WolfCrypt.WC_HASH_TYPE_SHA; this.oaepMgf = Rsa.WC_MGF1SHA1; + this.oaepSpec = new OAEPParameterSpec("SHA-1", "MGF1", + MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); } /** @@ -336,6 +358,9 @@ private void setOaepParams(OAEPParameterSpec spec) } } + /* Store spec for AlgorithmParameters retrieval */ + this.oaepSpec = spec; + /* Set OAEP hash type */ this.oaepHashType = hashNameToWolfCryptType(spec.getDigestAlgorithm()); @@ -739,6 +764,11 @@ else if (this.cipherType == CipherType.WC_DES3) { break; } + if (params == null && this.oaepSpec != null) { + params = AlgorithmParameters.getInstance("OAEP"); + params.init(this.oaepSpec); + } + } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) { /* Return null if parameter creation fails */ @@ -1052,8 +1082,20 @@ private void wolfCryptCipherInit(int opmode, Key key, wolfCryptSetDirection(opmode); wolfCryptSetIV(spec, random); wolfCryptSetKey(key); + this.finalized = false; this.operationStarted = false; this.cipherInitialized = true; + + /* Initialize streaming GCM encrypt if available */ + this.gcmStreamingActive = false; + this.gcmAadPassed = false; + this.gcmBytesEncrypted = 0L; + if (cipherMode == CipherMode.WC_GCM && + direction == OpMode.WC_ENCRYPT && + FeatureDetect.AesGcmStreamEnabled()) { + this.aesGcm.encryptInitStreaming(this.iv); + this.gcmStreamingActive = true; + } } @Override @@ -1152,12 +1194,16 @@ private boolean isNoOpUpdate(int inputSz) { } /* AES-GCM, AES-CCM, and AES-CTS keep all data buffered until - * final() call. wolfJCE does not support streaming GCM/CCM yet. - * CTS requires the entire message for ciphertext stealing. */ + * final() call. Streaming GCM encrypt bypasses this. CCM and CTS + * always buffer (CCM requires total length upfront, CTS needs full + * message for ciphertext stealing). */ if (cipherType == CipherType.WC_AES && (cipherMode == CipherMode.WC_GCM || cipherMode == CipherMode.WC_CCM || cipherMode == CipherMode.WC_CTS)) { + if (gcmStreamingActive) { + return false; + } return true; } @@ -1192,8 +1238,38 @@ private byte[] wolfCryptUpdate(byte[] input, int inputOffset, int len) "Input buffer length smaller than inputOffset + len"); } + if (len > 0 && (cipherMode == CipherMode.WC_GCM || + cipherMode == CipherMode.WC_CCM)) { + if ((buffered.length + len) > AEAD_MAX_PLAINTEXT) { + throw new IllegalStateException( + "AEAD plaintext exceeds maximum size of " + + AEAD_MAX_PLAINTEXT + " bytes"); + } + } + this.operationStarted = true; + /* Streaming GCM encrypt: process data immediately, skip buffering */ + if (gcmStreamingActive) { + if (len > 0 && gcmBytesEncrypted + len > GCM_MAX_PLAINTEXT_BYTES) { + throw new IllegalStateException( + "AES-GCM plaintext exceeds NIST SP 800-38D limit of " + + GCM_MAX_PLAINTEXT_BYTES + " bytes"); + } + byte[] aadForUpdate = gcmAadPassed ? null : this.aadData; + gcmAadPassed = true; + byte[] inputSlice = (len > 0) ? + Arrays.copyOfRange( + input, inputOffset, inputOffset + len) : + null; + byte[] ct = this.aesGcm.encryptUpdateStreaming(inputSlice, + aadForUpdate); + if (len > 0) { + gcmBytesEncrypted += len; + } + return (ct != null) ? ct : new byte[0]; + } + if ((buffered.length + len) == 0) { /* no data to process */ return null; @@ -1389,16 +1465,51 @@ private byte[] wolfCryptFinal(byte[] input, int inputOffset, int len) case WC_AES: if (cipherMode == CipherMode.WC_GCM) { if (this.direction == OpMode.WC_ENCRYPT) { - byte[] tag = new byte[this.gcmTagLen]; - tmpOut = this.aesGcm.encrypt(tmpIn, this.iv, tag, + if (gcmStreamingActive) { + /* Streaming path: all prior data was encrypted in + * update calls. Handle any remaining plaintext and + * generate the authentication tag. */ + if (!gcmAadPassed && this.aadData != null) { + /* AAD not yet submitted — pass it now */ + this.aesGcm.encryptUpdateStreaming(null, this.aadData); - - /* Concatenate auth tag to end of ciphertext */ - byte[] totalOut = new byte[tmpOut.length + tag.length]; - System.arraycopy(tmpOut, 0, totalOut, 0, tmpOut.length); - System.arraycopy(tag, 0, totalOut, tmpOut.length, - tag.length); - tmpOut = totalOut; + gcmAadPassed = true; + } + byte[] finalCt = null; + if (tmpIn.length > 0) { + if (gcmBytesEncrypted + tmpIn.length > + GCM_MAX_PLAINTEXT_BYTES) { + throw new IllegalBlockSizeException( + "AES-GCM plaintext exceeds NIST " + + "SP 800-38D limit of " + + GCM_MAX_PLAINTEXT_BYTES + " bytes"); + } + finalCt = this.aesGcm.encryptUpdateStreaming( + tmpIn, null); + gcmBytesEncrypted += tmpIn.length; + } + byte[] tag = this.aesGcm.encryptFinalStreaming( + this.gcmTagLen); + int ctLen = (finalCt != null) ? finalCt.length : 0; + tmpOut = new byte[ctLen + tag.length]; + if (ctLen > 0) { + System.arraycopy(finalCt, 0, tmpOut, 0, ctLen); + } + System.arraycopy(tag, 0, tmpOut, ctLen, tag.length); + } else { + byte[] tag = new byte[this.gcmTagLen]; + tmpOut = this.aesGcm.encrypt(tmpIn, this.iv, tag, + this.aadData); + + /* Concatenate auth tag to end of ciphertext */ + byte[] totalOut = + new byte[tmpOut.length + tag.length]; + System.arraycopy(tmpOut, 0, totalOut, 0, + tmpOut.length); + System.arraycopy(tag, 0, totalOut, tmpOut.length, + tag.length); + tmpOut = totalOut; + } } else { /* Case where input is only the authentication tag, @@ -1498,7 +1609,12 @@ else if (cipherMode == CipherMode.WC_OFB) { cipherMode != CipherMode.WC_CTR && cipherMode != CipherMode.WC_CTS && cipherMode != CipherMode.WC_OFB) { - tmpOut = Aes.unPadPKCS7(tmpOut, Aes.BLOCK_SIZE); + try { + tmpOut = Aes.unPadPKCS7(tmpOut, Aes.BLOCK_SIZE); + } catch (WolfCryptException e) { + throw new BadPaddingException( + "Invalid PKCS5/7 padding"); + } } } @@ -1514,7 +1630,12 @@ else if (cipherMode == CipherMode.WC_OFB) { if (tmpOut != null && tmpOut.length > 0) { if (this.direction == OpMode.WC_DECRYPT && this.paddingType == PaddingType.WC_PKCS5) { - tmpOut = Des3.unPadPKCS7(tmpOut, Des3.BLOCK_SIZE); + try { + tmpOut = Des3.unPadPKCS7(tmpOut, Des3.BLOCK_SIZE); + } catch (WolfCryptException e) { + throw new BadPaddingException( + "Invalid PKCS5/7 padding"); + } } } @@ -1621,6 +1742,17 @@ else if (cipherMode == CipherMode.WC_OFB) { this.operationStarted = false; this.cipherInitialized = true; + /* Re-initialize streaming GCM encrypt if available */ + this.gcmStreamingActive = false; + this.gcmAadPassed = false; + this.gcmBytesEncrypted = 0L; + if (cipherMode == CipherMode.WC_GCM && + direction == OpMode.WC_ENCRYPT && + FeatureDetect.AesGcmStreamEnabled()) { + this.aesGcm.encryptInitStreaming(this.iv); + this.gcmStreamingActive = true; + } + } catch (InvalidKeyException e) { throw new RuntimeException(e.getMessage()); } catch (InvalidAlgorithmParameterException e) { @@ -1636,6 +1768,11 @@ protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) byte output[]; + if (this.finalized) { + throw new IllegalStateException( + "Cipher has already been finalized, must re-init"); + } + if (!this.cipherInitialized) { throw new IllegalStateException( "Cipher has not been initialized yet"); @@ -1692,6 +1829,11 @@ protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte tmpOut[]; + if (this.finalized) { + throw new IllegalStateException( + "Cipher has already been finalized, must re-init"); + } + if (!this.cipherInitialized) { throw new IllegalStateException( "Cipher has not been initialized yet"); @@ -1745,6 +1887,11 @@ protected byte[] engineDoFinal(byte[] input, int inputOffset, throws IllegalStateException, IllegalBlockSizeException, BadPaddingException { + if (this.finalized) { + throw new IllegalStateException( + "Cipher has already been finalized, must re-init"); + } + if (!this.cipherInitialized) { throw new IllegalStateException( "Cipher has not been initialized yet"); @@ -1764,6 +1911,11 @@ protected int engineDoFinal(byte[] input, int inputOffset, byte tmpOut[]; + if (this.finalized) { + throw new IllegalStateException( + "Cipher has already been finalized, must re-init"); + } + if (!this.cipherInitialized) { throw new IllegalStateException( "Cipher has not been initialized yet"); @@ -1913,6 +2065,11 @@ protected byte[] engineWrap(Key key) byte[] encodedKey = null; byte[] wcBuf; + if (this.finalized) { + throw new IllegalStateException( + "Cipher has already been finalized, must re-init"); + } + if (key == null) { throw new InvalidKeyException( "Key to be wrapped must not be null"); @@ -1942,6 +2099,11 @@ protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgo, byte[] unwrappedKey; + if (this.finalized) { + throw new IllegalStateException( + "Cipher has already been finalized, must re-init"); + } + if (wrappedKey == null || wrappedKey.length == 0) { throw new InvalidKeyException( "Wrapped key bytes must not be null or empty"); diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java index b5451989..013f1bc9 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java @@ -303,9 +303,19 @@ private PrivateKey generatePrivateFromECSpec(ECPrivateKeySpec keySpec) "ECParameterSpec cannot be null"); } - /* Validate ECParameterSpec is supported by wolfCrypt, - * throws WolfCryptException if invalid */ - WolfCryptECParameterSpec.validateParameters(keySpec.getParams()); + /* Validate ECParameterSpec is supported by wolfCrypt. + * validateParameters throws IllegalArgumentException (unchecked) + * when the curve is not recognized; wrap it as + * InvalidKeySpecException so callers see a + * GeneralSecurityException. */ + try { + WolfCryptECParameterSpec.validateParameters( + keySpec.getParams()); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException( + "Unsupported curve parameters: " + + e.getMessage(), e); + } /* Get curve name from ECParameterSpec */ try { @@ -560,9 +570,19 @@ private PublicKey generatePublicFromECSpec(ECPublicKeySpec keySpec) "ECParameterSpec cannot be null"); } - /* Validate ECParameterSpec is supported by wolfCrypt, - * throws WolfCryptException if invalid */ - WolfCryptECParameterSpec.validateParameters(keySpec.getParams()); + /* Validate ECParameterSpec is supported by wolfCrypt. + * validateParameters throws IllegalArgumentException (unchecked) + * when the curve is not recognized; wrap it as + * InvalidKeySpecException so callers see a + * GeneralSecurityException. */ + try { + WolfCryptECParameterSpec.validateParameters( + keySpec.getParams()); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException( + "Unsupported curve parameters: " + + e.getMessage(), e); + } /* Get curve name from ECParameterSpec */ try { diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java index c1f90273..3062aced 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java @@ -47,6 +47,7 @@ import com.wolfssl.wolfcrypt.Dh; import com.wolfssl.wolfcrypt.Ecc; +import com.wolfssl.wolfcrypt.WolfCryptException; /** * wolfCrypt JCE Key Agreement wrapper @@ -70,6 +71,8 @@ enum EngineState { private Ecc ecPrivate = null; private int primeLen = 0; + private byte[] dhParamP = null; + private byte[] dhParamG = null; private int curveSize = 0; private String curveName = null; @@ -127,12 +130,39 @@ protected Key engineDoPhase(Key key, boolean lastPhase) "Key must be of type DHPublicKey"); } - pubKey = ((DHPublicKey)key).getY().toByteArray(); + DHPublicKey dhPubKey = (DHPublicKey)key; + + pubKey = dhPubKey.getY().toByteArray(); if (pubKey == null) { throw new InvalidKeyException( "Failed to get DH public key from Key object"); } + if (this.dhParamP == null || this.dhParamG == null) { + throw new InvalidKeyException( + "DH private key not initialized with parameters"); + } + + BigInteger privP = new BigInteger(this.dhParamP); + BigInteger privG = new BigInteger(this.dhParamG); + BigInteger pubPBig = dhPubKey.getParams().getP(); + BigInteger pubGBig = dhPubKey.getParams().getG(); + + if (!privP.equals(pubPBig) || !privG.equals(pubGBig)) { + throw new InvalidKeyException( + "DH public key parameters do not match " + + "private key parameters, cannot generate " + + "shared secret"); + } + + try { + this.dh.checkPublicKey(pubKey); + } catch (WolfCryptException e) { + throw new InvalidKeyException( + "DH public key value is invalid for the " + + "given group parameters", e); + } + this.dh.setPublicKey(pubKey); break; @@ -149,7 +179,38 @@ protected Key engineDoPhase(Key key, boolean lastPhase) "Failed to get ECC public key from Key object"); } - this.ecPublic.publicKeyDecode(pubKey); + try { + this.ecPublic.publicKeyDecode(pubKey); + } catch (WolfCryptException e) { + throw new InvalidKeyException( + "EC public key could not be decoded", e); + } + + try { + this.ecPublic.checkKey(); + } catch (WolfCryptException e) { + throw new InvalidKeyException( + "EC public key point is not on the curve", e); + } + + /* Verify the public key curve matches the private key curve. + * A curve mismatch (e.g. secp224r1 public against secp256r1 + * private) is caught here so the caller sees + * InvalidKeyException + * rather than a WolfCryptException from makeSharedSecret. */ + try { + int pubCurveId = this.ecPublic.getCurveId(); + int privCurveId = this.ecPrivate.getCurveId(); + if (pubCurveId != privCurveId) { + throw new InvalidKeyException( + "EC public key curve does not match private key " + + "curve (public curveId=" + pubCurveId + + ", private curveId=" + privCurveId + ")"); + } + } catch (WolfCryptException e) { + throw new InvalidKeyException( + "Failed to validate EC key curve compatibility", e); + } break; }; @@ -303,7 +364,13 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset) case WC_ECDH: - tmp = this.ecPrivate.makeSharedSecret(this.ecPublic); + try { + tmp = this.ecPrivate.makeSharedSecret(this.ecPublic); + } catch (WolfCryptException e) { + throw new IllegalStateException( + "Native ECDH shared secret generation failed: " + + e.getMessage(), e); + } if (tmp == null) { throw new RuntimeException("Error when creating ECDH " + "shared secret"); @@ -451,6 +518,9 @@ private void wcInitDHParams(Key key, AlgorithmParameterSpec params) this.dh.setParams(paramP, paramG); + this.dhParamP = paramP; + this.dhParamG = paramG; + primeLen = paramP.length; /* prime may have leading zero */ @@ -479,6 +549,9 @@ private void wcInitDHParams(Key key, AlgorithmParameterSpec params) this.dh.setParams(paramP, paramG); + this.dhParamP = paramP; + this.dhParamG = paramG; + primeLen = paramP.length; /* prime may have leading zero */ @@ -577,11 +650,22 @@ private void wcInitECDHParams(Key key, AlgorithmParameterSpec params) "ECC curve is null, please check algorithm parameters"); } + /* Release and recreate native structs to support re-initialization. + * JCE requires KeyAgreement.init() to be callable multiple times. */ + if (this.ecPrivate != null) { + this.ecPrivate.releaseNativeStruct(); + } + this.ecPrivate = new Ecc(); + if (this.ecPublic != null) { + this.ecPublic.releaseNativeStruct(); + } + this.ecPublic = new Ecc(); + privKeyBytes = ecKey.getS().toByteArray(); try { this.ecPrivate.importPrivateOnCurve(privKeyBytes, - null, this.curveName); + null, this.curveName); } finally { zeroArray(privKeyBytes); } diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptOaepParameters.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptOaepParameters.java new file mode 100644 index 00000000..41948c30 --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptOaepParameters.java @@ -0,0 +1,154 @@ +/* WolfCryptOaepParameters.java + * + * Copyright (C) 2006-2026 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.IOException; +import java.security.AlgorithmParametersSpi; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.security.spec.MGF1ParameterSpec; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; + +/** + * wolfCrypt JCE AlgorithmParametersSpi implementation for RSA-OAEP parameters + */ +public class WolfCryptOaepParameters extends AlgorithmParametersSpi { + + private OAEPParameterSpec oaepSpec; + + public WolfCryptOaepParameters() { + this.oaepSpec = OAEPParameterSpec.DEFAULT; + } + + @Override + protected void engineInit(AlgorithmParameterSpec paramSpec) + throws InvalidParameterSpecException { + + if (!(paramSpec instanceof OAEPParameterSpec)) { + throw new InvalidParameterSpecException( + "Only OAEPParameterSpec supported"); + } + + OAEPParameterSpec spec = (OAEPParameterSpec) paramSpec; + + if (!"MGF1".equals(spec.getMGFAlgorithm())) { + throw new InvalidParameterSpecException( + "Only MGF1 supported for OAEP, got: " + + spec.getMGFAlgorithm()); + } + + AlgorithmParameterSpec mgfParams = spec.getMGFParameters(); + if (!(mgfParams instanceof MGF1ParameterSpec)) { + throw new InvalidParameterSpecException( + "MGF parameters must be MGF1ParameterSpec"); + } + + String mgfDigest = ((MGF1ParameterSpec) mgfParams).getDigestAlgorithm(); + if (!isDigestSupported(mgfDigest)) { + throw new InvalidParameterSpecException( + "Unsupported MGF digest: " + mgfDigest); + } + + PSource pSource = spec.getPSource(); + if (pSource != null && pSource instanceof PSource.PSpecified) { + byte[] label = ((PSource.PSpecified) pSource).getValue(); + if (label != null && label.length > 0) { + throw new InvalidParameterSpecException( + "OAEP label (PSource) must be empty"); + } + } + + this.oaepSpec = spec; + } + + @Override + protected void engineInit(byte[] params) throws IOException { + throw new IOException("Encoded OAEP parameters not supported"); + } + + @Override + protected void engineInit(byte[] params, String format) + throws IOException { + throw new IOException("Encoded OAEP parameters not supported"); + } + + @Override + @SuppressWarnings("unchecked") + protected T engineGetParameterSpec( + Class paramSpec) throws InvalidParameterSpecException { + + if (this.oaepSpec == null) { + throw new InvalidParameterSpecException( + "OAEP parameters not initialized"); + } + + if (paramSpec == null) { + throw new InvalidParameterSpecException( + "Parameter spec class cannot be null"); + } + + if (paramSpec == OAEPParameterSpec.class || + paramSpec == AlgorithmParameterSpec.class) { + return (T) this.oaepSpec; + } + + throw new InvalidParameterSpecException( + "Unsupported parameter spec: " + paramSpec.getName()); + } + + @Override + protected byte[] engineGetEncoded() throws IOException { + throw new IOException("Encoded OAEP parameters not supported"); + } + + @Override + protected byte[] engineGetEncoded(String format) throws IOException { + throw new IOException("Encoded OAEP parameters not supported"); + } + + @Override + protected String engineToString() { + if (this.oaepSpec == null) { + return "WolfCryptOaepParameters[uninitialized]"; + } + + return "WolfCryptOaepParameters[" + + "digest=" + this.oaepSpec.getDigestAlgorithm() + + ", mgf=" + this.oaepSpec.getMGFAlgorithm() + "]"; + } + + private boolean isDigestSupported(String digestAlg) { + switch (digestAlg.toUpperCase()) { + case "SHA-1": + case "SHA-224": + case "SHA-256": + case "SHA-384": + case "SHA-512": + case "SHA-512/224": + case "SHA-512/256": + return true; + default: + return false; + } + } +} diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java index 8274c80b..927673db 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java @@ -504,6 +504,12 @@ private void registerServices() { put("Alg.Alias.Cipher.RSA/ECB/OAEPWithSHA1AndMGF1Padding", "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); } + + /* OAEP AlgorithmParameters */ + if (FeatureDetect.RsaOaepEnabled()) { + put("AlgorithmParameters.OAEP", + "com.wolfssl.provider.jce.WolfCryptOaepParameters"); + } } /* KeyAgreement */ diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java index b223952f..28431c2c 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java @@ -32,6 +32,7 @@ import java.security.spec.ECParameterSpec; import java.math.BigInteger; +import java.util.Arrays; import java.security.InvalidKeyException; import java.security.SignatureException; @@ -318,12 +319,55 @@ private void wolfCryptInitPrivateKey(PrivateKey key, byte[] encodedKey) switch (this.keyType) { - case WC_RSA: + case WC_RSA: { + BigInteger biN, biP, biQ; /* import private PKCS#8 */ this.rsa.decodePrivateKeyPKCS8(encodedKey); + /* export raw CRT parameters to validate key consistency */ + int keySize = this.rsa.getEncryptSize(); + byte[] n = new byte[keySize]; + byte[] e = new byte[keySize]; + byte[] d = new byte[keySize]; + byte[] p = new byte[keySize]; + byte[] q = new byte[keySize]; + byte[] dP = new byte[keySize]; + byte[] dQ = new byte[keySize]; + byte[] u = new byte[keySize]; + long[] nSz = new long[]{keySize}; + long[] eSz = new long[]{keySize}; + long[] dSz = new long[]{keySize}; + long[] pSz = new long[]{keySize}; + long[] qSz = new long[]{keySize}; + long[] dPSz = new long[]{keySize}; + long[] dQSz = new long[]{keySize}; + long[] uSz = new long[]{keySize}; + + try { + this.rsa.exportRawPrivateKey(n, nSz, e, eSz, d, dSz, + p, pSz, q, qSz, dP, dPSz, dQ, dQSz, u, uSz); + + biN = new BigInteger(1, Arrays.copyOf(n, (int)nSz[0])); + biP = new BigInteger(1, Arrays.copyOf(p, (int)pSz[0])); + biQ = new BigInteger(1, Arrays.copyOf(q, (int)qSz[0])); + + if (!biN.equals(biP.multiply(biQ))) { + throw new InvalidKeyException( + "RSA private key modulus n does not equal p * q"); + } + } finally { + /* Zero sensitive CRT components regardless of outcome */ + zeroArray(d); + zeroArray(p); + zeroArray(q); + zeroArray(dP); + zeroArray(dQ); + zeroArray(u); + } + break; + } case WC_ECDSA: diff --git a/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java b/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java index 3739cb6f..68785de1 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java +++ b/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java @@ -28,6 +28,15 @@ public class AesGcm extends NativeStruct { private WolfCryptState state = WolfCryptState.UNINITIALIZED; + /** Streaming operation state */ + private enum StreamingState { + /** No streaming operation in progress */ + IDLE, + /** After encryptInitStreaming(), before encryptFinalStreaming() */ + STREAMING + } + private StreamingState streamingState = StreamingState.IDLE; + /** Lock around object state */ protected final Object stateLock = new Object(); @@ -43,6 +52,10 @@ private native byte[] wc_AesGcmEncrypt(byte[] input, byte[] iv, byte[] authTagOut, byte[] authIn); private native byte[] wc_AesGcmDecrypt(byte[] input, byte[] iv, byte[] authTag, byte[] authIn); + private native void wc_AesGcmEncryptInitStreaming(byte[] iv); + private native byte[] wc_AesGcmEncryptUpdateStreaming(byte[] input, + byte[] authIn); + private native byte[] wc_AesGcmEncryptFinalStreaming(int tagLen); /** * Create a new AesGcm object. @@ -270,5 +283,94 @@ public synchronized byte[] decrypt(byte[] input, byte[] iv, byte[] authTag, return output; } + + /** + * Initialize streaming AES-GCM encryption. Key must already be loaded + * via setKey(). Only available when wolfSSL is compiled with + * WOLFSSL_AESGCM_STREAM. + * + * @param iv IV for AES-GCM operation + * @throws WolfCryptException if native operation fails or + * feature not compiled in + * @throws IllegalStateException if key not loaded or object released + */ + public synchronized void encryptInitStreaming(byte[] iv) + throws IllegalStateException, WolfCryptException { + + checkStateAndInitialize(); + throwIfKeyNotLoaded(); + + /* Allowed from IDLE or STREAMING (re-init resets a prior stream) */ + streamingState = StreamingState.STREAMING; + + synchronized (pointerLock) { + wc_AesGcmEncryptInitStreaming(iv); + } + } + + /** + * Streaming AES-GCM encrypt update. Encrypts plaintext and/or processes + * AAD. May be called multiple times between encryptInitStreaming() and + * encryptFinalStreaming(). + * + * @param input plaintext to encrypt, may be null or empty for AAD-only + * @param authIn additional authenticated data, may be null + * @return ciphertext bytes (same length as input) + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if key not loaded or object released + */ + public synchronized byte[] encryptUpdateStreaming(byte[] input, + byte[] authIn) throws IllegalStateException, WolfCryptException { + + byte[] output = null; + + checkStateAndInitialize(); + throwIfKeyNotLoaded(); + + if (streamingState != StreamingState.STREAMING) { + throw new IllegalStateException( + "encryptInitStreaming must be called before " + + "encryptUpdateStreaming (current state: IDLE)"); + } + + synchronized (pointerLock) { + output = wc_AesGcmEncryptUpdateStreaming(input, authIn); + } + + return output; + } + + /** + * Finalize streaming AES-GCM encryption and generate authentication tag. + * + * @param tagLen desired auth tag length in bytes (up to AES block size 16) + * @return authentication tag bytes of length tagLen + * @throws WolfCryptException if native operation fails + * @throws IllegalStateException if key not loaded or object released + */ + public synchronized byte[] encryptFinalStreaming(int tagLen) + throws IllegalStateException, WolfCryptException { + + byte[] tag = null; + + checkStateAndInitialize(); + throwIfKeyNotLoaded(); + + if (streamingState != StreamingState.STREAMING) { + throw new IllegalStateException( + "encryptInitStreaming must be called before " + + "encryptFinalStreaming (current state: IDLE)"); + } + + try { + synchronized (pointerLock) { + tag = wc_AesGcmEncryptFinalStreaming(tagLen); + } + } finally { + streamingState = StreamingState.IDLE; + } + + return tag; + } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/Dh.java b/src/main/java/com/wolfssl/wolfcrypt/Dh.java index 39a51159..47520c95 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Dh.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Dh.java @@ -180,6 +180,36 @@ private void init() { state = WolfCryptState.INITIALIZED; } + /** + * Set private key + * + * @param priv private key array + * + * @throws IllegalStateException if object fails to initialize, or if + * releaseNativeStruct() has been called and object has been + * released. + */ + /** + * Validate DH public key value against stored parameters. + * Checks that the public key Y is in valid range [2, p-2]. + * + * @param pub public key value to validate + * + * @throws WolfCryptException if validation fails + * @throws IllegalStateException if object fails to initialize, or if + * releaseNativeStruct() has been called and object has been + * released. + */ + public synchronized void checkPublicKey(byte[] pub) + throws WolfCryptException, IllegalStateException { + + checkStateAndInitialize(); + + synchronized (pointerLock) { + wc_DhCheckPubKey(pub); + } + } + /** * Set private key *