diff --git a/jni/include/com_wolfssl_wolfcrypt_AesGcm.h b/jni/include/com_wolfssl_wolfcrypt_AesGcm.h index 622c1cd7..9eae7b3e 100644 --- a/jni/include/com_wolfssl_wolfcrypt_AesGcm.h +++ b/jni/include/com_wolfssl_wolfcrypt_AesGcm.h @@ -9,6 +9,8 @@ extern "C" { #endif #undef com_wolfssl_wolfcrypt_AesGcm_NULL #define com_wolfssl_wolfcrypt_AesGcm_NULL 0LL +#undef com_wolfssl_wolfcrypt_AesGcm_GCM_NONCE_MID_SZ +#define com_wolfssl_wolfcrypt_AesGcm_GCM_NONCE_MID_SZ 12L /* * Class: com_wolfssl_wolfcrypt_AesGcm * Method: mallocNativeStruct_internal @@ -73,6 +75,14 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmSetIV JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncrypt_1ex (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray, jbyteArray); +/* + * Class: com_wolfssl_wolfcrypt_AesGcm + * Method: wc_AesGcmShortIvAllowed + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmShortIvAllowed + (JNIEnv *, jclass); + #ifdef __cplusplus } #endif diff --git a/jni/jni_aesgcm.c b/jni/jni_aesgcm.c index 4c3c72a5..828b38cf 100644 --- a/jni/jni_aesgcm.c +++ b/jni/jni_aesgcm.c @@ -45,6 +45,24 @@ /* #define WOLFCRYPT_JNI_DEBUG_ON */ #include +JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmShortIvAllowed + (JNIEnv* env, jclass jcl) +{ + jboolean allowed = JNI_TRUE; + (void)env; + (void)jcl; + +#if defined(HAVE_FIPS) && !defined(FIPS_NO_WRAPPERS) && \ + defined(FIPS_VERSION3_EQ) && defined(FIPS_VERSION3_GE) + /* FIPS returns WC_FIPS_NOT_APPROVED for GCM/GMAC IVs under 96 bits */ + #if FIPS_VERSION3_EQ(5,2,4) || FIPS_VERSION3_GE(7,0,0) + allowed = JNI_FALSE; + #endif +#endif + + return allowed; +} + JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_mallocNativeStruct_1internal (JNIEnv* env, jobject this) { diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java index 923270f4..2229cc93 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java @@ -1267,6 +1267,12 @@ else if (cipherMode == CipherMode.WC_CCM) { "AES-GCM IV is null or 0 length"); } + if (!AesGcm.shortIvAllowed() && + gcmSpec.getIV().length < AesGcm.GCM_NONCE_MID_SZ) { + throw new InvalidAlgorithmParameterException( + "AES-GCM IV shorter than 96 bits is not FIPS approved"); + } + this.iv = gcmSpec.getIV().clone(); this.gcmTagLen = tagLenToBytes("AES-GCM", gcmSpec.getTLen()); } diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptMac.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptMac.java index e73c541d..9794eb35 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptMac.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptMac.java @@ -42,6 +42,7 @@ import com.wolfssl.wolfcrypt.Hmac; import com.wolfssl.wolfcrypt.AesCmac; import com.wolfssl.wolfcrypt.AesGmac; +import com.wolfssl.wolfcrypt.AesGcm; import com.wolfssl.wolfcrypt.Aes; /** @@ -256,6 +257,12 @@ protected void engineInit(Key key, AlgorithmParameterSpec params) " bits, got " + gmacTagBits); } + if (!AesGcm.shortIvAllowed() && + gcmSpec.getIV().length < AesGcm.GCM_NONCE_MID_SZ) { + throw new InvalidAlgorithmParameterException( + "AES-GMAC IV shorter than 96 bits not FIPS approved"); + } + this.aesGmac.setKey(encodedKey); this.gmacIv = gcmSpec.getIV(); /* Convert bits to bytes */ diff --git a/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java b/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java index b447b801..3f3d1dc8 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java +++ b/src/main/java/com/wolfssl/wolfcrypt/AesGcm.java @@ -26,6 +26,9 @@ */ public class AesGcm extends NativeStruct { + /** 96-bit AES-GCM IV size in bytes, native GCM_NONCE_MID_SZ */ + public static final int GCM_NONCE_MID_SZ = 12; + private WolfCryptState state = WolfCryptState.UNINITIALIZED; /* Size of IV generated by setIV(), 0 until setIV() is called */ @@ -49,6 +52,24 @@ private native byte[] wc_AesGcmDecrypt(byte[] input, byte[] iv, private native byte[] wc_AesGcmSetIV(int ivSz, byte[] ivFixed, Rng rng); private native byte[] wc_AesGcmEncrypt_ex(byte[] input, byte[] ivOut, byte[] authTagOut, byte[] authIn); + private static native boolean wc_AesGcmShortIvAllowed(); + + /** + * Checks if short AES-GCM and GMAC IVs (under 96 bits) are allowed. + * wolfCrypt FIPS v5.2.4 and v7+ mark them as not FIPS approved. + * + * @return true if IVs shorter than 96 bits are allowed + */ + public static boolean shortIvAllowed() { + + if (Fips.enabled) { + if (!wc_AesGcmShortIvAllowed()) { + return false; + } + } + + return true; + } /** * Create a new AesGcm object. diff --git a/src/main/java/com/wolfssl/wolfcrypt/WolfCryptError.java b/src/main/java/com/wolfssl/wolfcrypt/WolfCryptError.java index 47ae33dd..07d26da5 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/WolfCryptError.java +++ b/src/main/java/com/wolfssl/wolfcrypt/WolfCryptError.java @@ -31,6 +31,9 @@ public enum WolfCryptError { /** No error found */ NO_ERROR_FOUND (-1), + /** FIPS 140-3 service indicator, operation is not FIPS approved */ + WC_FIPS_NOT_APPROVED (1), + /* error codes match /wolfssl/wolfcrypt/error-crypt.h */ /** errors -97 - -1008 */ @@ -834,8 +837,12 @@ public int getCode() { * @return String description of current error */ public String getDescription() { - if (this == WolfCryptError.NO_ERROR_FOUND) + if (this == WolfCryptError.NO_ERROR_FOUND) { return "No error code found in JNI WolfCryptError enum"; + } + if (this == WolfCryptError.WC_FIPS_NOT_APPROVED) { + return "Operation is not FIPS approved"; + } return wc_GetErrorString(this.code); } 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 74e95405..625169d7 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java @@ -85,6 +85,7 @@ import com.wolfssl.wolfcrypt.FeatureDetect; import com.wolfssl.wolfcrypt.test.Util; import com.wolfssl.wolfcrypt.Aes; +import com.wolfssl.wolfcrypt.AesGcm; import com.wolfssl.wolfcrypt.AesXts; import com.wolfssl.wolfcrypt.Fips; import com.wolfssl.wolfcrypt.test.Util; @@ -10240,6 +10241,42 @@ public void nextBytes(byte[] bytes) { } } + /** + * AES-GCM IV shorter than 96 bits is rejected at init() where FIPS + * v5.2.4 and v7+ mark it not approved, and works otherwise. + */ + @Test + public void testAesGcmShortIv() throws Exception { + + if (!enabledJCEAlgos.contains("AES/GCM/NoPadding") || + !FeatureDetect.Aes128Enabled()) { + return; + } + + byte[] pt = "AES-GCM short IV".getBytes(); + SecretKeySpec key = new SecretKeySpec(new byte[16], "AES"); + GCMParameterSpec spec = new GCMParameterSpec(128, new byte[8]); + Cipher c = Cipher.getInstance("AES/GCM/NoPadding", jceProvider); + + if (AesGcm.shortIvAllowed()) { + c.init(Cipher.ENCRYPT_MODE, key, spec); + byte[] ct = c.doFinal(pt); + c.init(Cipher.DECRYPT_MODE, key, spec); + assertArrayEquals(pt, c.doFinal(ct)); + } + else { + for (int mode : new int[] {Cipher.ENCRYPT_MODE, + Cipher.DECRYPT_MODE}) { + try { + c.init(mode, key, spec); + fail("AES-GCM init with 8-byte IV should throw"); + } catch (InvalidAlgorithmParameterException e) { + /* expected */ + } + } + } + } + /** * Ciphertext from a wolfJCE AES/GCM/NoPadding encrypt init without * parameters decrypts with the interop provider using getIV(). diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMacTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMacTest.java index f92a1788..2cc08c71 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMacTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptMacTest.java @@ -51,6 +51,7 @@ import java.security.InvalidAlgorithmParameterException; import com.wolfssl.wolfcrypt.Fips; +import com.wolfssl.wolfcrypt.AesGcm; import com.wolfssl.provider.jce.WolfCryptProvider; import com.wolfssl.wolfcrypt.test.Util; import com.wolfssl.wolfcrypt.test.TimedTestWatcher; @@ -2194,6 +2195,33 @@ public void testAesGmacRejectsInvalidTagLength() assertEquals(16, mac.getMacLength()); } + @Test + public void testAesGmacShortIv() + throws InvalidKeyException, NoSuchAlgorithmException, + NoSuchProviderException, InvalidAlgorithmParameterException { + + if (!enabledAlgos.contains("AESGMAC")) { + return; + } + + Mac mac = Mac.getInstance("AESGMAC", "wolfJCE"); + SecretKeySpec keyspec = new SecretKeySpec(new byte[16], "AES"); + GCMParameterSpec spec = new GCMParameterSpec(128, new byte[8]); + + if (AesGcm.shortIvAllowed()) { + mac.init(keyspec, spec); + assertEquals(16, mac.doFinal().length); + } + else { + try { + mac.init(keyspec, spec); + fail("AES-GMAC init with 8-byte IV should throw"); + } catch (InvalidAlgorithmParameterException e) { + /* expected */ + } + } + } + @Test public void testAesGmacRejectedInitPreservesState() throws InvalidKeyException, NoSuchAlgorithmException, diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTest.java index 3bec01e3..0ba96ee0 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTest.java @@ -157,6 +157,13 @@ public void testGetDigestSize() { } } + @Test + public void testFipsNotApprovedError() { + WolfCryptException e = new WolfCryptException(1); + assertEquals(WolfCryptError.WC_FIPS_NOT_APPROVED, e.getError()); + assertEquals("Operation is not FIPS approved", e.getMessage()); + } + @Test public void testToHexStringBasic() throws Exception {