![JAR search and dependency download from the Maven repository](/logo.png)
nl.open.jwtdependency.org.bouncycastle.crypto.test.AEADTestUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-jwt-nodependencies Show documentation
Show all versions of java-jwt-nodependencies Show documentation
This is a drop in replacement for the auth0 java-jwt library (see https://github.com/auth0/java-jwt). This jar makes sure there are no external dependencies (e.g. fasterXml, Apacha Commons) needed. This is useful when deploying to an application server (e.g. tomcat with Alfreso or Pega).
The newest version!
package org.bouncycastle.crypto.test;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.crypto.modes.AEADBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.test.SimpleTestResult;
import org.bouncycastle.util.test.Test;
import org.bouncycastle.util.test.TestFailedException;
public class AEADTestUtil
{
public static void testTampering(Test test, AEADBlockCipher cipher, CipherParameters params)
throws InvalidCipherTextException
{
byte[] plaintext = new byte[1000];
for (int i = 0; i < plaintext.length; i++)
{
plaintext[i] = (byte)i;
}
cipher.init(true, params);
byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)];
int len = cipher.processBytes(plaintext, 0, plaintext.length, ciphertext, 0);
cipher.doFinal(ciphertext, len);
int macLength = cipher.getMac().length;
// Test tampering with a single byte
cipher.init(false, params);
byte[] tampered = new byte[ciphertext.length];
byte[] output = new byte[plaintext.length];
System.arraycopy(ciphertext, 0, tampered, 0, tampered.length);
tampered[0] += 1;
cipher.processBytes(tampered, 0, tampered.length, output, 0);
try
{
cipher.doFinal(output, 0);
throw new TestFailedException(
new SimpleTestResult(false, test + " : tampering of ciphertext not detected."));
}
catch (InvalidCipherTextException e)
{
// Expected
}
// Test truncation of ciphertext to < tag length
cipher.init(false, params);
byte[] truncated = new byte[macLength - 1];
System.arraycopy(ciphertext, 0, truncated, 0, truncated.length);
cipher.processBytes(truncated, 0, truncated.length, output, 0);
try
{
cipher.doFinal(output, 0);
fail(test, "tampering of ciphertext not detected.");
}
catch (InvalidCipherTextException e)
{
// Expected
}
}
private static void fail(Test test, String message)
{
throw new TestFailedException(SimpleTestResult.failed(test, message));
}
private static void fail(Test test, String message, String expected, String result)
{
throw new TestFailedException(SimpleTestResult.failed(test, message, expected, result));
}
public static void testReset(Test test, AEADBlockCipher cipher1, AEADBlockCipher cipher2, CipherParameters params)
throws InvalidCipherTextException
{
cipher1.init(true, params);
byte[] plaintext = new byte[1000];
byte[] ciphertext = new byte[cipher1.getOutputSize(plaintext.length)];
// Establish baseline answer
crypt(cipher1, plaintext, ciphertext);
// Test encryption resets
checkReset(test, cipher1, params, true, plaintext, ciphertext);
// Test decryption resets with fresh instance
cipher2.init(false, params);
checkReset(test, cipher2, params, false, ciphertext, plaintext);
}
private static void checkReset(Test test,
AEADBlockCipher cipher,
CipherParameters params,
boolean encrypt,
byte[] pretext,
byte[] posttext)
throws InvalidCipherTextException
{
// Do initial run
byte[] output = new byte[posttext.length];
crypt(cipher, pretext, output);
// Check encrypt resets cipher
crypt(cipher, pretext, output);
if (!Arrays.areEqual(output, posttext))
{
fail(test, (encrypt ? "Encrypt" : "Decrypt") + " did not reset cipher.");
}
// Check init resets data
cipher.processBytes(pretext, 0, 100, output, 0);
cipher.init(encrypt, params);
try
{
crypt(cipher, pretext, output);
}
catch (DataLengthException e)
{
fail(test, "Init did not reset data.");
}
if (!Arrays.areEqual(output, posttext))
{
fail(test, "Init did not reset data.", new String(Hex.encode(posttext)), new String(Hex.encode(output)));
}
// Check init resets AD
cipher.processAADBytes(pretext, 0, 100);
cipher.init(encrypt, params);
try
{
crypt(cipher, pretext, output);
}
catch (DataLengthException e)
{
fail(test, "Init did not reset additional data.");
}
if (!Arrays.areEqual(output, posttext))
{
fail(test, "Init did not reset additional data.");
}
// Check reset resets data
cipher.processBytes(pretext, 0, 100, output, 0);
cipher.reset();
try
{
crypt(cipher, pretext, output);
}
catch (DataLengthException e)
{
fail(test, "Init did not reset data.");
}
if (!Arrays.areEqual(output, posttext))
{
fail(test, "Reset did not reset data.");
}
// Check reset resets AD
cipher.processAADBytes(pretext, 0, 100);
cipher.reset();
try
{
crypt(cipher, pretext, output);
}
catch (DataLengthException e)
{
fail(test, "Init did not reset data.");
}
if (!Arrays.areEqual(output, posttext))
{
fail(test, "Reset did not reset additional data.");
}
}
private static void crypt(AEADBlockCipher cipher, byte[] plaintext, byte[] output)
throws InvalidCipherTextException
{
int len = cipher.processBytes(plaintext, 0, plaintext.length, output, 0);
cipher.doFinal(output, len);
}
public static void testOutputSizes(Test test, AEADBlockCipher cipher, AEADParameters params)
throws IllegalStateException,
InvalidCipherTextException
{
int maxPlaintext = cipher.getUnderlyingCipher().getBlockSize() * 10;
byte[] plaintext = new byte[maxPlaintext];
byte[] ciphertext = new byte[maxPlaintext * 2];
// Check output size calculations for truncated ciphertext lengths
cipher.init(true, params);
cipher.doFinal(ciphertext, 0);
int macLength = cipher.getMac().length;
cipher.init(false, params);
for (int i = 0; i < macLength; i++)
{
cipher.reset();
if (cipher.getUpdateOutputSize(i) != 0)
{
fail(test, "AE cipher should not produce update output with ciphertext length <= macSize");
}
if (cipher.getOutputSize(i) != 0)
{
fail(test, "AE cipher should not produce output with ciphertext length <= macSize");
}
}
for (int i = 0; i < plaintext.length; i++)
{
cipher.init(true, params);
int expectedCTUpdateSize = cipher.getUpdateOutputSize(i);
int expectedCTOutputSize = cipher.getOutputSize(i);
if (expectedCTUpdateSize < 0)
{
fail(test, "Encryption update output size should not be < 0 for size " + i);
}
if (expectedCTOutputSize < 0)
{
fail(test, "Encryption update output size should not be < 0 for size " + i);
}
int actualCTSize = cipher.processBytes(plaintext, 0, i, ciphertext, 0);
if (expectedCTUpdateSize != actualCTSize)
{
fail(test, "Encryption update output size did not match calculated for plaintext length " + i,
String.valueOf(expectedCTUpdateSize), String.valueOf(actualCTSize));
}
actualCTSize += cipher.doFinal(ciphertext, actualCTSize);
if (expectedCTOutputSize != actualCTSize)
{
fail(test, "Encryption actual final output size did not match calculated for plaintext length " + i,
String.valueOf(expectedCTOutputSize), String.valueOf(actualCTSize));
}
cipher.init(false, params);
int expectedPTUpdateSize = cipher.getUpdateOutputSize(actualCTSize);
int expectedPTOutputSize = cipher.getOutputSize(actualCTSize);
if (expectedPTOutputSize != i)
{
fail(test, "Decryption update output size did not original plaintext length " + i,
String.valueOf(expectedPTUpdateSize), String.valueOf(i));
}
int actualPTSize = cipher.processBytes(ciphertext, 0, actualCTSize, plaintext, 0);
if (expectedPTUpdateSize != actualPTSize)
{
fail(test, "Decryption update output size did not match calculated for plaintext length " + i,
String.valueOf(expectedPTUpdateSize), String.valueOf(actualPTSize));
}
actualPTSize += cipher.doFinal(plaintext, actualPTSize);
if (expectedPTOutputSize != actualPTSize)
{
fail(test, "Decryption update output size did not match calculated for plaintext length " + i,
String.valueOf(expectedPTOutputSize), String.valueOf(actualPTSize));
}
}
}
public static void testBufferSizeChecks(Test test, AEADBlockCipher cipher, AEADParameters params)
throws IllegalStateException,
InvalidCipherTextException
{
int blockSize = cipher.getUnderlyingCipher().getBlockSize();
int maxPlaintext = (blockSize * 10);
byte[] plaintext = new byte[maxPlaintext];
cipher.init(true, params);
int expectedUpdateOutputSize = cipher.getUpdateOutputSize(plaintext.length);
byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)];
try
{
cipher.processBytes(new byte[maxPlaintext - 1], 0, maxPlaintext, new byte[expectedUpdateOutputSize], 0);
fail(test, "processBytes should validate input buffer length");
}
catch (DataLengthException e)
{
// Expected
}
cipher.reset();
if (expectedUpdateOutputSize > 0)
{
int outputTrigger = 0;
// Process bytes until output would be produced
for(int i = 0; i < plaintext.length; i++) {
if (cipher.getUpdateOutputSize(1) != 0)
{
outputTrigger = i + 1;
break;
}
cipher.processByte(plaintext[i], ciphertext, 0);
}
if (outputTrigger == 0)
{
fail(test, "Failed to find output trigger size");
}
try
{
cipher.processByte(plaintext[0], new byte[cipher.getUpdateOutputSize(1) - 1], 0);
fail(test, "Encrypt processByte should validate output buffer length");
}
catch (OutputLengthException e)
{
// Expected
}
cipher.reset();
// Repeat checking with entire input at once
try
{
cipher.processBytes(plaintext, 0, outputTrigger,
new byte[cipher.getUpdateOutputSize(outputTrigger) - 1], 0);
fail(test, "Encrypt processBytes should validate output buffer length");
}
catch (OutputLengthException e)
{
// Expected
}
cipher.reset();
}
// Remember the actual ciphertext for later
int actualOutputSize = cipher.processBytes(plaintext, 0, plaintext.length, ciphertext, 0);
actualOutputSize += cipher.doFinal(ciphertext, actualOutputSize);
int macSize = cipher.getMac().length;
cipher.reset();
try
{
cipher.processBytes(plaintext, 0, plaintext.length, ciphertext, 0);
cipher.doFinal(new byte[cipher.getOutputSize(0) - 1], 0);
fail(test, "Encrypt doFinal should validate output buffer length");
}
catch (OutputLengthException e)
{
// Expected
}
// Decryption tests
cipher.init(false, params);
expectedUpdateOutputSize = cipher.getUpdateOutputSize(actualOutputSize);
if (expectedUpdateOutputSize > 0)
{
// Process bytes until output would be produced
int outputTrigger = 0;
for (int i = 0; i < plaintext.length; i++)
{
if (cipher.getUpdateOutputSize(1) != 0)
{
outputTrigger = i + 1;
break;
}
cipher.processByte(ciphertext[i], plaintext, 0);
}
if (outputTrigger == 0)
{
fail(test, "Failed to find output trigger size");
}
try
{
cipher.processByte(ciphertext[0], new byte[cipher.getUpdateOutputSize(1) - 1], 0);
fail(test, "Decrypt processByte should validate output buffer length");
}
catch (OutputLengthException e)
{
// Expected
}
cipher.reset();
// Repeat test with processBytes
try
{
cipher.processBytes(ciphertext, 0, outputTrigger,
new byte[cipher.getUpdateOutputSize(outputTrigger) - 1], 0);
fail(test, "Decrypt processBytes should validate output buffer length");
}
catch (OutputLengthException e)
{
// Expected
}
}
cipher.reset();
// Data less than mac length should fail before output length check
try
{
// Assumes AE cipher on decrypt can't return any data until macSize bytes are received
if (cipher.processBytes(ciphertext, 0, macSize - 1, plaintext, 0) != 0)
{
fail(test, "AE cipher unexpectedly produced output");
}
cipher.doFinal(new byte[0], 0);
fail(test, "Decrypt doFinal should check ciphertext length");
}
catch (InvalidCipherTextException e)
{
// Expected
}
try
{
// Search through plaintext lengths until one is found that creates >= 1 buffered byte
// during decryption of ciphertext for doFinal to handle
for (int i = 2; i < plaintext.length; i++)
{
cipher.init(true, params);
int encrypted = cipher.processBytes(plaintext, 0, i, ciphertext, 0);
encrypted += cipher.doFinal(ciphertext, encrypted);
cipher.init(false, params);
cipher.processBytes(ciphertext, 0, encrypted - 1, plaintext, 0);
if (cipher.processByte(ciphertext[encrypted - 1], plaintext, 0) == 0)
{
cipher.doFinal(new byte[cipher.getOutputSize(0) - 1], 0);
fail(test, "Decrypt doFinal should check output length");
cipher.reset();
// Truncated Mac should be reported in preference to inability to output
// buffered plaintext byte
try
{
cipher.processBytes(ciphertext, 0, actualOutputSize - 1, plaintext, 0);
cipher.doFinal(new byte[cipher.getOutputSize(0) - 1], 0);
fail(test, "Decrypt doFinal should check ciphertext length");
}
catch (InvalidCipherTextException e)
{
// Expected
}
cipher.reset();
}
}
fail(test, "Decrypt doFinal test couldn't find a ciphertext length that buffered for doFinal");
}
catch (OutputLengthException e)
{
// Expected
}
}
static AEADParameters reuseKey(AEADParameters p)
{
return new AEADParameters(null, p.getMacSize(), p.getNonce(), p.getAssociatedText());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy