Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions jni/jni_aesgcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,10 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncrypt
authInSz = (*env)->GetArrayLength(env, authInArr);
}

/* authIn can be null */
if (in == NULL || inLen == 0 || iv == NULL || ivSz == 0 ||
authTag == NULL || authTagSz == 0) {
/* in may be null, users might only pass in AAD to generate tag */
if (authTagSz > WC_AES_BLOCK_SIZE || iv == NULL || ivSz == 0 ||
((authTagSz > 0) && (authTag == NULL)) ||
((authInSz > 0) && (authIn == NULL))) {
ret = BAD_FUNC_ARG;
}

Expand Down Expand Up @@ -325,8 +326,10 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmDecrypt
authInSz = (*env)->GetArrayLength(env, authInArr);
}

if (in == NULL || inLen == 0 || iv == NULL || ivSz == 0 ||
authTag == NULL || authTagSz == 0) {
/* If inLen is non-zero, both in and out must be set. If inLen is 0,
* in and out are don't cares, as this is the GMAC case */
if (iv == NULL || ivSz == 0 || (inLen != 0 && in == NULL) ||
authTag == NULL || (authTagSz > WC_AES_BLOCK_SIZE) || authTagSz == 0) {
ret = BAD_FUNC_ARG;
}

Expand Down
9 changes: 6 additions & 3 deletions src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java
Original file line number Diff line number Diff line change
Expand Up @@ -979,9 +979,12 @@ private byte[] wolfCryptFinal(byte[] input, int inputOffset, int len)

/* do final encrypt over totalSz */
tmpIn = new byte[totalSz];
System.arraycopy(buffered, 0, tmpIn, 0, buffered.length);
if (input != null && len > 0) {
System.arraycopy(input, inputOffset, tmpIn, buffered.length, len);
if (totalSz > 0) {
System.arraycopy(buffered, 0, tmpIn, 0, buffered.length);
if (input != null && len > 0) {
System.arraycopy(input, inputOffset, tmpIn,
buffered.length, len);
}
}

/* add padding if encrypting and PKCS5 padding is used. PKCS#5 padding
Expand Down
108 changes: 108 additions & 0 deletions src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6483,5 +6483,113 @@ public void testGetOutputSizeZeroInputPKCS5Padding() throws Exception {
"block (8 bytes)", 8, outputSize);
}
}

/**
* Test AES-GCM with null plaintext input. This tests scenarios where
* users may only provide AAD to generate an authentication tag.
* Uses test vectors from OpenJDK SunJCE tests that have null plaintext.
*/
@Test
public void testAesGcmWithNullPlaintext()
throws NoSuchAlgorithmException, InvalidKeyException,
IllegalBlockSizeException, NoSuchProviderException,
InvalidAlgorithmParameterException, BadPaddingException,
NoSuchPaddingException {

if (!enabledJCEAlgos.contains("AES/GCM/NoPadding")) {
/* skip if AES-GCM is not enabled */
return;
}

/*
* Test vector from OpenJDK TestKATForGCM.java - Test case 1
* 96-bit iv with 128-bit tag, no plaintext, no AAD
*/
byte[] key = new byte[] {
(byte)0x11, (byte)0x75, (byte)0x4c, (byte)0xd7,
(byte)0x2a, (byte)0xec, (byte)0x30, (byte)0x9b,
(byte)0xf5, (byte)0x2f, (byte)0x76, (byte)0x87,
(byte)0x21, (byte)0x2e, (byte)0x89, (byte)0x57
};
byte[] iv = new byte[] {
(byte)0x3c, (byte)0x81, (byte)0x9d, (byte)0x9a,
(byte)0x9b, (byte)0xed, (byte)0x08, (byte)0x76,
(byte)0x15, (byte)0x03, (byte)0x0b, (byte)0x65
};
byte[] expectedTag = new byte[] {
(byte)0x25, (byte)0x03, (byte)0x27, (byte)0xc6,
(byte)0x74, (byte)0xaa, (byte)0xf4, (byte)0x77,
(byte)0xae, (byte)0xf2, (byte)0x67, (byte)0x57,
(byte)0x48, (byte)0xcf, (byte)0x69, (byte)0x71
};

/*
* Test vector from OpenJDK TestKATForGCM.java - Test case 6
* 96-bit iv with 128-bit tag, no plaintext, 16-byte AAD
*/
byte[] key2 = new byte[] {
(byte)0x77, (byte)0xbe, (byte)0x63, (byte)0x70,
(byte)0x89, (byte)0x71, (byte)0xc4, (byte)0xe2,
(byte)0x40, (byte)0xd1, (byte)0xcb, (byte)0x79,
(byte)0xe8, (byte)0xd7, (byte)0x7f, (byte)0xeb
};
byte[] iv2 = new byte[] {
(byte)0xe0, (byte)0xe0, (byte)0x0f, (byte)0x19,
(byte)0xfe, (byte)0xd7, (byte)0xba, (byte)0x01,
(byte)0x36, (byte)0xa7, (byte)0x97, (byte)0xf3
};
byte[] aad2 = new byte[] {
(byte)0x7a, (byte)0x43, (byte)0xec, (byte)0x1d,
(byte)0x9c, (byte)0x0a, (byte)0x5a, (byte)0x78,
(byte)0xa0, (byte)0xb1, (byte)0x65, (byte)0x33,
(byte)0xa6, (byte)0x21, (byte)0x3c, (byte)0xab
};
byte[] expectedTag2 = new byte[] {
(byte)0x20, (byte)0x9f, (byte)0xcc, (byte)0x8d,
(byte)0x36, (byte)0x75, (byte)0xed, (byte)0x93,
(byte)0x8e, (byte)0x9c, (byte)0x71, (byte)0x66,
(byte)0x70, (byte)0x9d, (byte)0xd9, (byte)0x46
};

/* Using byte[0] instead of null because OpenJDK Cipher.java
* throws IllegalArgumentException when in is null. */
byte[] nullInput = new byte[0];

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", jceProvider);

/* Test case 1: No plaintext, no AAD */
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
GCMParameterSpec gcmSpec = new GCMParameterSpec(
expectedTag.length * 8, iv);

cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
byte[] output = cipher.doFinal(nullInput);

/* Output should just be the tag since no ciphertext */
assertArrayEquals("Tag should match expected for null plaintext",
expectedTag, output);

/* Test case 2: No plaintext, with AAD */
SecretKeySpec keySpec2 = new SecretKeySpec(key2, "AES");
GCMParameterSpec gcmSpec2 = new GCMParameterSpec(
expectedTag2.length * 8, iv2);

cipher.init(Cipher.ENCRYPT_MODE, keySpec2, gcmSpec2);
cipher.updateAAD(aad2);
byte[] output2 = cipher.doFinal(nullInput);

/* Output should just be the tag since no ciphertext */
assertArrayEquals("Tag should match expected for null plaintext " +
"with AAD", expectedTag2, output2);

/* Verify decryption works too */
cipher.init(Cipher.DECRYPT_MODE, keySpec2, gcmSpec2);
cipher.updateAAD(aad2);
byte[] decrypted = cipher.doFinal(output2);

/* Decrypted should be empty/null since original plaintext was null */
assertTrue("Decrypted plaintext should be empty when original " +
"plaintext was null", decrypted == null || decrypted.length == 0);
}
}

136 changes: 118 additions & 18 deletions src/test/java/com/wolfssl/wolfcrypt/test/AesGcmTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -341,12 +341,11 @@ public void testAesGcm128() throws WolfCryptException {
plain = dec.decrypt(cipher, iv3, tag, a3);
assertArrayEquals(p3, plain);

/* bad encrypt arguments: null input */
/* encrypt with null input should pass */
try {
enc.encrypt(null, iv3, tag, a3);
fail("encrypt() with null input should fail");
} catch (WolfCryptException e) {
/* expected */
fail("encrypt() with null input should pass");
}

/* bad encrypt arguments: null iv */
Expand All @@ -365,12 +364,11 @@ public void testAesGcm128() throws WolfCryptException {
/* expected */
}

/* bad decrypt arguments: null input */
/* decrypt with null input but valid tag and AAD should pass */
try {
enc.decrypt(null, iv3, tag, a3);
fail("decrypt() with null input should fail");
} catch (WolfCryptException e) {
/* expected */
fail("decrypt() with null input should pass");
}

/* bad decrypt arguments: null iv */
Expand Down Expand Up @@ -422,12 +420,11 @@ public void testAesGcm192() throws WolfCryptException {
assertNotNull(plain);
assertArrayEquals(p, plain);

/* bad encrypt arguments: null input */
/* encrypt with null input should pass */
try {
enc.encrypt(null, iv2, tag, a);
fail("encrypt() with null input should fail");
} catch (WolfCryptException e) {
/* expected */
fail("encrypt() with null input should pass");
}

/* bad encrypt arguments: null iv */
Expand All @@ -446,12 +443,11 @@ public void testAesGcm192() throws WolfCryptException {
/* expected */
}

/* bad decrypt arguments: null input */
/* decrypt with null input but valid tag and AAD should pass */
try {
enc.decrypt(null, iv2, tag, a);
fail("decrypt() with null input should fail");
} catch (WolfCryptException e) {
/* expected */
fail("decrypt() with null input should pass");
}

/* bad decrypt arguments: null iv */
Expand Down Expand Up @@ -503,12 +499,11 @@ public void testAesGcm256() throws WolfCryptException {
assertNotNull(plain);
assertArrayEquals(p, plain);

/* bad encrypt arguments: null input */
/* encrypt with null input should pass */
try {
enc.encrypt(null, iv1, tag, a);
fail("encrypt() with null input should fail");
} catch (WolfCryptException e) {
/* expected */
fail("encrypt() with null input should pass");
}

/* bad encrypt arguments: null iv */
Expand All @@ -527,12 +522,11 @@ public void testAesGcm256() throws WolfCryptException {
/* expected */
}

/* bad decrypt arguments: null input */
/* decrypt with null input but valid tag and AAD should pass */
try {
enc.decrypt(null, iv1, tag, a);
fail("decrypt() with null input should fail");
} catch (WolfCryptException e) {
/* expected */
fail("decrypt() with null input should pass");
}

/* bad decrypt arguments: null iv */
Expand Down Expand Up @@ -1042,5 +1036,111 @@ public void testThreadedAes256() throws InterruptedException {
}
}
}

/**
* Test AES-GCM with null plaintext using test vectors from
* OpenJDK TestKATForGCM.java that have null plaintext input.
* This tests scenarios where users may only provide AAD to
* generate an authentication tag.
*/
@Test
public void testAesGcmWithNullPlaintext() throws WolfCryptException {

/*
* Test vector 1 from OpenJDK: AES-128, 96-bit IV,
* no plaintext, no AAD, 128-bit tag
*/
byte[] key1 = new byte[] {
(byte)0x11, (byte)0x75, (byte)0x4c, (byte)0xd7,
(byte)0x2a, (byte)0xec, (byte)0x30, (byte)0x9b,
(byte)0xf5, (byte)0x2f, (byte)0x76, (byte)0x87,
(byte)0x21, (byte)0x2e, (byte)0x89, (byte)0x57
};
byte[] iv1 = new byte[] {
(byte)0x3c, (byte)0x81, (byte)0x9d, (byte)0x9a,
(byte)0x9b, (byte)0xed, (byte)0x08, (byte)0x76,
(byte)0x15, (byte)0x03, (byte)0x0b, (byte)0x65
};
byte[] expectedTag1 = new byte[] {
(byte)0x25, (byte)0x03, (byte)0x27, (byte)0xc6,
(byte)0x74, (byte)0xaa, (byte)0xf4, (byte)0x77,
(byte)0xae, (byte)0xf2, (byte)0x67, (byte)0x57,
(byte)0x48, (byte)0xcf, (byte)0x69, (byte)0x71
};

/*
* Test vector 6 from OpenJDK: AES-128, 96-bit IV,
* no plaintext, 16-byte AAD, 128-bit tag
*/
byte[] key2 = new byte[] {
(byte)0x77, (byte)0xbe, (byte)0x63, (byte)0x70,
(byte)0x89, (byte)0x71, (byte)0xc4, (byte)0xe2,
(byte)0x40, (byte)0xd1, (byte)0xcb, (byte)0x79,
(byte)0xe8, (byte)0xd7, (byte)0x7f, (byte)0xeb
};
byte[] iv2 = new byte[] {
(byte)0xe0, (byte)0xe0, (byte)0x0f, (byte)0x19,
(byte)0xfe, (byte)0xd7, (byte)0xba, (byte)0x01,
(byte)0x36, (byte)0xa7, (byte)0x97, (byte)0xf3
};
byte[] aad2 = new byte[] {
(byte)0x7a, (byte)0x43, (byte)0xec, (byte)0x1d,
(byte)0x9c, (byte)0x0a, (byte)0x5a, (byte)0x78,
(byte)0xa0, (byte)0xb1, (byte)0x65, (byte)0x33,
(byte)0xa6, (byte)0x21, (byte)0x3c, (byte)0xab
};
byte[] expectedTag2 = new byte[] {
(byte)0x20, (byte)0x9f, (byte)0xcc, (byte)0x8d,
(byte)0x36, (byte)0x75, (byte)0xed, (byte)0x93,
(byte)0x8e, (byte)0x9c, (byte)0x71, (byte)0x66,
(byte)0x70, (byte)0x9d, (byte)0xd9, (byte)0x46
};

/* skip test if AES-128 is not compiled in native library */
if (!FeatureDetect.Aes128Enabled()) {
return;
}

/* Test case 1: null plaintext, no AAD */
AesGcm enc = new AesGcm();
enc.setKey(key1);
byte[] tag = new byte[expectedTag1.length];
byte[] ciphertext = enc.encrypt(null, iv1, tag, null);

/* Should return null/empty ciphertext since input was null */
assertTrue("Ciphertext should be null or empty when input is null",
ciphertext == null || ciphertext.length == 0);

/* Tag should match expected value */
assertArrayEquals("Tag should match expected value for null " +
"plaintext, no AAD", expectedTag1, tag);
enc.releaseNativeStruct();

/* Test case 2: null plaintext, with AAD */
enc = new AesGcm();
enc.setKey(key2);
tag = new byte[expectedTag2.length];
ciphertext = enc.encrypt(null, iv2, tag, aad2);

/* Should return null/empty ciphertext since input was null */
assertTrue("Ciphertext should be null or empty when input is null",
ciphertext == null || ciphertext.length == 0);

/* Tag should match expected value */
assertArrayEquals("Tag should match expected value for null " +
"plaintext with AAD", expectedTag2, tag);

/* Test decryption with null ciphertext */
AesGcm dec = new AesGcm();
dec.setKey(key2);
byte[] plaintext = dec.decrypt(null, iv2, tag, aad2);

/* Should return null/empty plaintext since ciphertext was null */
assertTrue("Plaintext should be null or empty when ciphertext " +
"is null", plaintext == null || plaintext.length == 0);

enc.releaseNativeStruct();
dec.releaseNativeStruct();
}
}

Loading