Skip to content

Commit d0fe8ae

Browse files
committed
JNI/JCE: fix AES-GCM edge cases to allow for null input or output arrays, add test cases
1 parent 5a1846c commit d0fe8ae

File tree

4 files changed

+240
-26
lines changed

4 files changed

+240
-26
lines changed

jni/jni_aesgcm.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,10 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmEncrypt
192192
authInSz = (*env)->GetArrayLength(env, authInArr);
193193
}
194194

195-
/* authIn can be null */
196-
if (in == NULL || inLen == 0 || iv == NULL || ivSz == 0 ||
197-
authTag == NULL || authTagSz == 0) {
195+
/* in can be null, users might only pass in AAD to generate tag */
196+
if (authTagSz > WC_AES_BLOCK_SIZE || iv == NULL || ivSz == 0 ||
197+
((authTagSz > 0) && (authTag == NULL)) ||
198+
((authInSz > 0) && (authIn == NULL))) {
198199
ret = BAD_FUNC_ARG;
199200
}
200201

@@ -325,8 +326,10 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_AesGcm_wc_1AesGcmDecrypt
325326
authInSz = (*env)->GetArrayLength(env, authInArr);
326327
}
327328

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

src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -979,9 +979,12 @@ private byte[] wolfCryptFinal(byte[] input, int inputOffset, int len)
979979

980980
/* do final encrypt over totalSz */
981981
tmpIn = new byte[totalSz];
982-
System.arraycopy(buffered, 0, tmpIn, 0, buffered.length);
983-
if (input != null && len > 0) {
984-
System.arraycopy(input, inputOffset, tmpIn, buffered.length, len);
982+
if (totalSz > 0) {
983+
System.arraycopy(buffered, 0, tmpIn, 0, buffered.length);
984+
if (input != null && len > 0) {
985+
System.arraycopy(input, inputOffset, tmpIn,
986+
buffered.length, len);
987+
}
985988
}
986989

987990
/* add padding if encrypting and PKCS5 padding is used. PKCS#5 padding

src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6483,5 +6483,113 @@ public void testGetOutputSizeZeroInputPKCS5Padding() throws Exception {
64836483
"block (8 bytes)", 8, outputSize);
64846484
}
64856485
}
6486+
6487+
/**
6488+
* Test AES-GCM with null plaintext input. This tests scenarios where
6489+
* users may only provide AAD to generate an authentication tag.
6490+
* Uses test vectors from OpenJDK SunJCE tests that have null plaintext.
6491+
*/
6492+
@Test
6493+
public void testAesGcmWithNullPlaintext()
6494+
throws NoSuchAlgorithmException, InvalidKeyException,
6495+
IllegalBlockSizeException, NoSuchProviderException,
6496+
InvalidAlgorithmParameterException, BadPaddingException,
6497+
NoSuchPaddingException {
6498+
6499+
if (!enabledJCEAlgos.contains("AES/GCM/NoPadding")) {
6500+
/* skip if AES-GCM is not enabled */
6501+
return;
6502+
}
6503+
6504+
/*
6505+
* Test vector from OpenJDK TestKATForGCM.java - Test case 1
6506+
* 96-bit iv with 128-bit tag, no plaintext, no AAD
6507+
*/
6508+
byte[] key = new byte[] {
6509+
(byte)0x11, (byte)0x75, (byte)0x4c, (byte)0xd7,
6510+
(byte)0x2a, (byte)0xec, (byte)0x30, (byte)0x9b,
6511+
(byte)0xf5, (byte)0x2f, (byte)0x76, (byte)0x87,
6512+
(byte)0x21, (byte)0x2e, (byte)0x89, (byte)0x57
6513+
};
6514+
byte[] iv = new byte[] {
6515+
(byte)0x3c, (byte)0x81, (byte)0x9d, (byte)0x9a,
6516+
(byte)0x9b, (byte)0xed, (byte)0x08, (byte)0x76,
6517+
(byte)0x15, (byte)0x03, (byte)0x0b, (byte)0x65
6518+
};
6519+
byte[] expectedTag = new byte[] {
6520+
(byte)0x25, (byte)0x03, (byte)0x27, (byte)0xc6,
6521+
(byte)0x74, (byte)0xaa, (byte)0xf4, (byte)0x77,
6522+
(byte)0xae, (byte)0xf2, (byte)0x67, (byte)0x57,
6523+
(byte)0x48, (byte)0xcf, (byte)0x69, (byte)0x71
6524+
};
6525+
6526+
/*
6527+
* Test vector from OpenJDK TestKATForGCM.java - Test case 6
6528+
* 96-bit iv with 128-bit tag, no plaintext, 16-byte AAD
6529+
*/
6530+
byte[] key2 = new byte[] {
6531+
(byte)0x77, (byte)0xbe, (byte)0x63, (byte)0x70,
6532+
(byte)0x89, (byte)0x71, (byte)0xc4, (byte)0xe2,
6533+
(byte)0x40, (byte)0xd1, (byte)0xcb, (byte)0x79,
6534+
(byte)0xe8, (byte)0xd7, (byte)0x7f, (byte)0xeb
6535+
};
6536+
byte[] iv2 = new byte[] {
6537+
(byte)0xe0, (byte)0xe0, (byte)0x0f, (byte)0x19,
6538+
(byte)0xfe, (byte)0xd7, (byte)0xba, (byte)0x01,
6539+
(byte)0x36, (byte)0xa7, (byte)0x97, (byte)0xf3
6540+
};
6541+
byte[] aad2 = new byte[] {
6542+
(byte)0x7a, (byte)0x43, (byte)0xec, (byte)0x1d,
6543+
(byte)0x9c, (byte)0x0a, (byte)0x5a, (byte)0x78,
6544+
(byte)0xa0, (byte)0xb1, (byte)0x65, (byte)0x33,
6545+
(byte)0xa6, (byte)0x21, (byte)0x3c, (byte)0xab
6546+
};
6547+
byte[] expectedTag2 = new byte[] {
6548+
(byte)0x20, (byte)0x9f, (byte)0xcc, (byte)0x8d,
6549+
(byte)0x36, (byte)0x75, (byte)0xed, (byte)0x93,
6550+
(byte)0x8e, (byte)0x9c, (byte)0x71, (byte)0x66,
6551+
(byte)0x70, (byte)0x9d, (byte)0xd9, (byte)0x46
6552+
};
6553+
6554+
/* Using byte[0] instead of null because OpenJDK Cipher.java
6555+
* throws IllegalArgumentException when in is null. */
6556+
byte[] nullInput = new byte[0];
6557+
6558+
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", jceProvider);
6559+
6560+
/* Test case 1: No plaintext, no AAD */
6561+
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
6562+
GCMParameterSpec gcmSpec = new GCMParameterSpec(
6563+
expectedTag.length * 8, iv);
6564+
6565+
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
6566+
byte[] output = cipher.doFinal(nullInput);
6567+
6568+
/* Output should just be the tag since no ciphertext */
6569+
assertArrayEquals("Tag should match expected for null plaintext",
6570+
expectedTag, output);
6571+
6572+
/* Test case 2: No plaintext, with AAD */
6573+
SecretKeySpec keySpec2 = new SecretKeySpec(key2, "AES");
6574+
GCMParameterSpec gcmSpec2 = new GCMParameterSpec(
6575+
expectedTag2.length * 8, iv2);
6576+
6577+
cipher.init(Cipher.ENCRYPT_MODE, keySpec2, gcmSpec2);
6578+
cipher.updateAAD(aad2);
6579+
byte[] output2 = cipher.doFinal(nullInput);
6580+
6581+
/* Output should just be the tag since no ciphertext */
6582+
assertArrayEquals("Tag should match expected for null plaintext " +
6583+
"with AAD", expectedTag2, output2);
6584+
6585+
/* Verify decryption works too */
6586+
cipher.init(Cipher.DECRYPT_MODE, keySpec2, gcmSpec2);
6587+
cipher.updateAAD(aad2);
6588+
byte[] decrypted = cipher.doFinal(output2);
6589+
6590+
/* Decrypted should be empty/null since original plaintext was null */
6591+
assertTrue("Decrypted plaintext should be empty when original " +
6592+
"plaintext was null", decrypted == null || decrypted.length == 0);
6593+
}
64866594
}
64876595

src/test/java/com/wolfssl/wolfcrypt/test/AesGcmTest.java

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -341,12 +341,11 @@ public void testAesGcm128() throws WolfCryptException {
341341
plain = dec.decrypt(cipher, iv3, tag, a3);
342342
assertArrayEquals(p3, plain);
343343

344-
/* bad encrypt arguments: null input */
344+
/* encrypt with null input should pass */
345345
try {
346346
enc.encrypt(null, iv3, tag, a3);
347-
fail("encrypt() with null input should fail");
348347
} catch (WolfCryptException e) {
349-
/* expected */
348+
fail("encrypt() with null input should pass");
350349
}
351350

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

368-
/* bad decrypt arguments: null input */
367+
/* decrypt with null input but valid tag and AAD should pass */
369368
try {
370369
enc.decrypt(null, iv3, tag, a3);
371-
fail("decrypt() with null input should fail");
372370
} catch (WolfCryptException e) {
373-
/* expected */
371+
fail("decrypt() with null input should pass");
374372
}
375373

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

425-
/* bad encrypt arguments: null input */
423+
/* encrypt with null input should pass */
426424
try {
427425
enc.encrypt(null, iv2, tag, a);
428-
fail("encrypt() with null input should fail");
429426
} catch (WolfCryptException e) {
430-
/* expected */
427+
fail("encrypt() with null input should pass");
431428
}
432429

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

449-
/* bad decrypt arguments: null input */
446+
/* decrypt with null input but valid tag and AAD should pass */
450447
try {
451448
enc.decrypt(null, iv2, tag, a);
452-
fail("decrypt() with null input should fail");
453449
} catch (WolfCryptException e) {
454-
/* expected */
450+
fail("decrypt() with null input should pass");
455451
}
456452

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

506-
/* bad encrypt arguments: null input */
502+
/* encrypt with null input should pass */
507503
try {
508504
enc.encrypt(null, iv1, tag, a);
509-
fail("encrypt() with null input should fail");
510505
} catch (WolfCryptException e) {
511-
/* expected */
506+
fail("encrypt() with null input should pass");
512507
}
513508

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

530-
/* bad decrypt arguments: null input */
525+
/* decrypt with null input but valid tag and AAD should pass */
531526
try {
532527
enc.decrypt(null, iv1, tag, a);
533-
fail("decrypt() with null input should fail");
534528
} catch (WolfCryptException e) {
535-
/* expected */
529+
fail("decrypt() with null input should pass");
536530
}
537531

538532
/* bad decrypt arguments: null iv */
@@ -1042,5 +1036,111 @@ public void testThreadedAes256() throws InterruptedException {
10421036
}
10431037
}
10441038
}
1039+
1040+
/**
1041+
* Test AES-GCM with null plaintext using test vectors from
1042+
* OpenJDK TestKATForGCM.java that have null plaintext input.
1043+
* This tests scenarios where users may only provide AAD to
1044+
* generate an authentication tag.
1045+
*/
1046+
@Test
1047+
public void testAesGcmWithNullPlaintext() throws WolfCryptException {
1048+
1049+
/*
1050+
* Test vector 1 from OpenJDK: AES-128, 96-bit IV,
1051+
* no plaintext, no AAD, 128-bit tag
1052+
*/
1053+
byte[] key1 = new byte[] {
1054+
(byte)0x11, (byte)0x75, (byte)0x4c, (byte)0xd7,
1055+
(byte)0x2a, (byte)0xec, (byte)0x30, (byte)0x9b,
1056+
(byte)0xf5, (byte)0x2f, (byte)0x76, (byte)0x87,
1057+
(byte)0x21, (byte)0x2e, (byte)0x89, (byte)0x57
1058+
};
1059+
byte[] iv1 = new byte[] {
1060+
(byte)0x3c, (byte)0x81, (byte)0x9d, (byte)0x9a,
1061+
(byte)0x9b, (byte)0xed, (byte)0x08, (byte)0x76,
1062+
(byte)0x15, (byte)0x03, (byte)0x0b, (byte)0x65
1063+
};
1064+
byte[] expectedTag1 = new byte[] {
1065+
(byte)0x25, (byte)0x03, (byte)0x27, (byte)0xc6,
1066+
(byte)0x74, (byte)0xaa, (byte)0xf4, (byte)0x77,
1067+
(byte)0xae, (byte)0xf2, (byte)0x67, (byte)0x57,
1068+
(byte)0x48, (byte)0xcf, (byte)0x69, (byte)0x71
1069+
};
1070+
1071+
/*
1072+
* Test vector 6 from OpenJDK: AES-128, 96-bit IV,
1073+
* no plaintext, 16-byte AAD, 128-bit tag
1074+
*/
1075+
byte[] key2 = new byte[] {
1076+
(byte)0x77, (byte)0xbe, (byte)0x63, (byte)0x70,
1077+
(byte)0x89, (byte)0x71, (byte)0xc4, (byte)0xe2,
1078+
(byte)0x40, (byte)0xd1, (byte)0xcb, (byte)0x79,
1079+
(byte)0xe8, (byte)0xd7, (byte)0x7f, (byte)0xeb
1080+
};
1081+
byte[] iv2 = new byte[] {
1082+
(byte)0xe0, (byte)0xe0, (byte)0x0f, (byte)0x19,
1083+
(byte)0xfe, (byte)0xd7, (byte)0xba, (byte)0x01,
1084+
(byte)0x36, (byte)0xa7, (byte)0x97, (byte)0xf3
1085+
};
1086+
byte[] aad2 = new byte[] {
1087+
(byte)0x7a, (byte)0x43, (byte)0xec, (byte)0x1d,
1088+
(byte)0x9c, (byte)0x0a, (byte)0x5a, (byte)0x78,
1089+
(byte)0xa0, (byte)0xb1, (byte)0x65, (byte)0x33,
1090+
(byte)0xa6, (byte)0x21, (byte)0x3c, (byte)0xab
1091+
};
1092+
byte[] expectedTag2 = new byte[] {
1093+
(byte)0x20, (byte)0x9f, (byte)0xcc, (byte)0x8d,
1094+
(byte)0x36, (byte)0x75, (byte)0xed, (byte)0x93,
1095+
(byte)0x8e, (byte)0x9c, (byte)0x71, (byte)0x66,
1096+
(byte)0x70, (byte)0x9d, (byte)0xd9, (byte)0x46
1097+
};
1098+
1099+
/* skip test if AES-128 is not compiled in native library */
1100+
if (!FeatureDetect.Aes128Enabled()) {
1101+
return;
1102+
}
1103+
1104+
/* Test case 1: null plaintext, no AAD */
1105+
AesGcm enc = new AesGcm();
1106+
enc.setKey(key1);
1107+
byte[] tag = new byte[expectedTag1.length];
1108+
byte[] ciphertext = enc.encrypt(null, iv1, tag, null);
1109+
1110+
/* Should return null/empty ciphertext since input was null */
1111+
assertTrue("Ciphertext should be null or empty when input is null",
1112+
ciphertext == null || ciphertext.length == 0);
1113+
1114+
/* Tag should match expected value */
1115+
assertArrayEquals("Tag should match expected value for null " +
1116+
"plaintext, no AAD", expectedTag1, tag);
1117+
enc.releaseNativeStruct();
1118+
1119+
/* Test case 2: null plaintext, with AAD */
1120+
enc = new AesGcm();
1121+
enc.setKey(key2);
1122+
tag = new byte[expectedTag2.length];
1123+
ciphertext = enc.encrypt(null, iv2, tag, aad2);
1124+
1125+
/* Should return null/empty ciphertext since input was null */
1126+
assertTrue("Ciphertext should be null or empty when input is null",
1127+
ciphertext == null || ciphertext.length == 0);
1128+
1129+
/* Tag should match expected value */
1130+
assertArrayEquals("Tag should match expected value for null " +
1131+
"plaintext with AAD", expectedTag2, tag);
1132+
1133+
/* Test decryption with null ciphertext */
1134+
AesGcm dec = new AesGcm();
1135+
dec.setKey(key2);
1136+
byte[] plaintext = dec.decrypt(null, iv2, tag, aad2);
1137+
1138+
/* Should return null/empty plaintext since ciphertext was null */
1139+
assertTrue("Plaintext should be null or empty when ciphertext " +
1140+
"is null", plaintext == null || plaintext.length == 0);
1141+
1142+
enc.releaseNativeStruct();
1143+
dec.releaseNativeStruct();
1144+
}
10451145
}
10461146

0 commit comments

Comments
 (0)