F-5798: reject non-byte-aligned AES-GCM/CCM tag lengths at Cipher init
parent
f1a011956e
commit
97842c5087
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue