/prov/src/test/java/org/bouncycastle/jce/provider/test/CipherStreamTest2.java
Java | 519 lines | 424 code | 59 blank | 36 comment | 44 complexity | 7123ee3849b88c66828ed9a284a565dc MD5 | raw file
- package org.bouncycastle.jce.provider.test;
- import java.io.*;
- import java.security.Key;
- import java.security.Security;
- import javax.crypto.*;
- import javax.crypto.spec.IvParameterSpec;
- import org.bouncycastle.crypto.io.InvalidCipherTextIOException;
- import org.bouncycastle.jcajce.io.CipherInputStream;
- import org.bouncycastle.jcajce.io.CipherOutputStream;
- import org.bouncycastle.jce.provider.BouncyCastleProvider;
- import org.bouncycastle.util.Arrays;
- import org.bouncycastle.util.test.SimpleTest;
- public class CipherStreamTest2
- extends SimpleTest
- {
- private int streamSize;
- public String getName()
- {
- return "CipherStreamTest2";
- }
- private void testModes(String algo, String[] transforms, boolean authenticated)
- throws Exception
- {
- Key key = generateKey(algo);
- for (int i = 0; i != transforms.length; i++)
- {
- String transform = transforms[i];
- String cipherName = algo + transform;
- boolean cts = transform.indexOf("CTS") > -1;
- if (cts && streamSize < Cipher.getInstance(cipherName, "BC").getBlockSize())
- {
- continue;
- }
- testWriteRead(cipherName, key, authenticated, true, false);
- testWriteRead(cipherName, key, authenticated, true, true);
- testWriteRead(cipherName, key, authenticated, false, false);
- testWriteRead(cipherName, key, authenticated, false, true);
- testReadWrite(cipherName, key, authenticated, true, false);
- testReadWrite(cipherName, key, authenticated, true, true);
- testReadWrite(cipherName, key, authenticated, false, false);
- testReadWrite(cipherName, key, authenticated, false, true);
- if (!cts)
- {
- testWriteReadEmpty(cipherName, key, authenticated, true, false);
- testWriteReadEmpty(cipherName, key, authenticated, true, true);
- testWriteReadEmpty(cipherName, key, authenticated, false, false);
- testWriteReadEmpty(cipherName, key, authenticated, false, true);
- }
- if (authenticated)
- {
- testTamperedRead(cipherName, key, true, true);
- testTamperedRead(cipherName, key, true, false);
- testTruncatedRead(cipherName, key, true, true);
- testTruncatedRead(cipherName, key, true, false);
- testTamperedWrite(cipherName, key, true, true);
- testTamperedWrite(cipherName, key, true, false);
- }
- }
- }
- private InputStream createInputStream(byte[] data, Cipher cipher, boolean useBc)
- {
- ByteArrayInputStream bytes = new ByteArrayInputStream(data);
- // cast required for earlier JDK
- return useBc ? (InputStream)new CipherInputStream(bytes, cipher) : (InputStream)new javax.crypto.CipherInputStream(bytes, cipher);
- }
- private OutputStream createOutputStream(ByteArrayOutputStream bytes, Cipher cipher, boolean useBc)
- {
- // cast required for earlier JDK
- return useBc ? (OutputStream)new CipherOutputStream(bytes, cipher) : (OutputStream)new javax.crypto.CipherOutputStream(bytes, cipher);
- }
- /**
- * Test tampering of ciphertext followed by read from decrypting CipherInputStream
- */
- private void testTamperedRead(String name, Key key, boolean authenticated, boolean useBc)
- throws Exception
- {
- Cipher encrypt = Cipher.getInstance(name, "BC");
- Cipher decrypt = Cipher.getInstance(name, "BC");
- encrypt.init(Cipher.ENCRYPT_MODE, key);
- if (encrypt.getIV() != null)
- {
- decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(encrypt.getIV()));
- }
- else
- {
- decrypt.init(Cipher.DECRYPT_MODE, key);
- }
- byte[] ciphertext = encrypt.doFinal(new byte[streamSize]);
- // Tamper
- ciphertext[0] += 1;
- InputStream input = createInputStream(ciphertext, decrypt, useBc);
- try
- {
- while (input.read() >= 0)
- {
- }
- fail("Expected invalid ciphertext after tamper and read : " + name, authenticated, useBc);
- }
- catch (InvalidCipherTextIOException e)
- {
- // Expected
- }
- catch (IOException e) // cause will be AEADBadTagException
- {
- // Expected
- }
- try
- {
- input.close();
- }
- catch (Exception e)
- {
- fail("Unexpected exception : " + name, e, authenticated, useBc);
- }
- }
- /**
- * Test truncation of ciphertext to make tag calculation impossible, followed by read from
- * decrypting CipherInputStream
- */
- private void testTruncatedRead(String name, Key key, boolean authenticated, boolean useBc)
- throws Exception
- {
- Cipher encrypt = Cipher.getInstance(name, "BC");
- Cipher decrypt = Cipher.getInstance(name, "BC");
- encrypt.init(Cipher.ENCRYPT_MODE, key);
- if (encrypt.getIV() != null)
- {
- decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(encrypt.getIV()));
- }
- else
- {
- decrypt.init(Cipher.DECRYPT_MODE, key);
- }
- byte[] ciphertext = encrypt.doFinal(new byte[streamSize]);
- // Truncate to just smaller than complete tag
- byte[] truncated = new byte[ciphertext.length - streamSize - 1];
- System.arraycopy(ciphertext, 0, truncated, 0, truncated.length);
- // Tamper
- ciphertext[0] += 1;
- InputStream input = createInputStream(truncated, decrypt, useBc);
- while (true)
- {
- int read = 0;
- try
- {
- read = input.read();
- }
- catch (InvalidCipherTextIOException e)
- {
- // Expected
- break;
- }
- catch (IOException e)
- {
- // Expected from JDK 1.7 on
- break;
- }
- catch (Exception e)
- {
- fail("Unexpected exception : " + name, e, authenticated, useBc);
- break;
- }
- if (read < 0)
- {
- fail("Expected invalid ciphertext after truncate and read : " + name, authenticated, useBc);
- break;
- }
- }
- try
- {
- input.close();
- }
- catch (Exception e)
- {
- fail("Unexpected exception : " + name, e, authenticated, useBc);
- }
- }
- /**
- * Test tampering of ciphertext followed by write to decrypting CipherOutputStream
- */
- private void testTamperedWrite(String name, Key key, boolean authenticated, boolean useBc)
- throws Exception
- {
- Cipher encrypt = Cipher.getInstance(name, "BC");
- Cipher decrypt = Cipher.getInstance(name, "BC");
- encrypt.init(Cipher.ENCRYPT_MODE, key);
- if (encrypt.getIV() != null)
- {
- decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(encrypt.getIV()));
- }
- else
- {
- decrypt.init(Cipher.DECRYPT_MODE, key);
- }
- byte[] ciphertext = encrypt.doFinal(new byte[streamSize]);
- // Tamper
- ciphertext[0] += 1;
- ByteArrayOutputStream plaintext = new ByteArrayOutputStream();
- OutputStream output = createOutputStream(plaintext, decrypt, useBc);
- for (int i = 0; i < ciphertext.length; i++)
- {
- output.write(ciphertext[i]);
- }
- try
- {
- output.close();
- fail("Expected invalid ciphertext after tamper and write : " + name, authenticated, useBc);
- }
- catch (InvalidCipherTextIOException e)
- {
- // Expected
- }
- }
- /**
- * Test CipherOutputStream in ENCRYPT_MODE, CipherInputStream in DECRYPT_MODE
- */
- private void testWriteRead(String name, Key key, boolean authenticated, boolean useBc, boolean blocks)
- throws Exception
- {
- byte[] data = new byte[streamSize];
- for (int i = 0; i < data.length; i++)
- {
- data[i] = (byte)(i % 255);
- }
- testWriteRead(name, key, authenticated, useBc, blocks, data);
- }
- /**
- * Test CipherOutputStream in ENCRYPT_MODE, CipherInputStream in DECRYPT_MODE
- */
- private void testWriteReadEmpty(String name, Key key, boolean authenticated, boolean useBc, boolean blocks)
- throws Exception
- {
- byte[] data = new byte[0];
- testWriteRead(name, key, authenticated, useBc, blocks, data);
- }
- private void testWriteRead(String name, Key key, boolean authenticated, boolean useBc, boolean blocks, byte[] data)
- {
- ByteArrayOutputStream bOut = new ByteArrayOutputStream();
- try
- {
- Cipher encrypt = Cipher.getInstance(name, "BC");
- Cipher decrypt = Cipher.getInstance(name, "BC");
- encrypt.init(Cipher.ENCRYPT_MODE, key);
- if (encrypt.getIV() != null)
- {
- decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(encrypt.getIV()));
- }
- else
- {
- decrypt.init(Cipher.DECRYPT_MODE, key);
- }
- OutputStream cOut = createOutputStream(bOut, encrypt, useBc);
- if (blocks)
- {
- int chunkSize = Math.max(1, data.length / 8);
- for (int i = 0; i < data.length; i += chunkSize)
- {
- cOut.write(data, i, Math.min(chunkSize, data.length - i));
- }
- }
- else
- {
- for (int i = 0; i < data.length; i++)
- {
- cOut.write(data[i]);
- }
- }
- cOut.close();
- byte[] cipherText = bOut.toByteArray();
- bOut.reset();
- InputStream cIn = createInputStream(cipherText, decrypt, useBc);
- if (blocks)
- {
- byte[] block = new byte[encrypt.getBlockSize() + 1];
- int c;
- while ((c = cIn.read(block)) >= 0)
- {
- bOut.write(block, 0, c);
- }
- }
- else
- {
- int c;
- while ((c = cIn.read()) >= 0)
- {
- bOut.write(c);
- }
- }
- cIn.close();
- }
- catch (Exception e)
- {
- fail("Unexpected exception " + name, e, authenticated, useBc);
- }
- byte[] decrypted = bOut.toByteArray();
- if (!Arrays.areEqual(data, decrypted))
- {
- fail("Failed - decrypted data doesn't match: " + name, authenticated, useBc);
- }
- }
- protected void fail(String message, boolean authenticated, boolean bc)
- {
- if (bc || !authenticated)
- {
- super.fail(message);
- }
- else
- {
- // javax.crypto.CipherInputStream/CipherOutputStream
- // are broken wrt handling AEAD failures
- // System.err.println("Broken JCE Streams: " + message);
- }
- }
- protected void fail(String message, Throwable throwable, boolean authenticated, boolean bc)
- {
- if (bc || !authenticated)
- {
- super.fail(message, throwable);
- }
- else
- {
- // javax.crypto.CipherInputStream/CipherOutputStream
- // are broken wrt handling AEAD failures
- //System.err.println("Broken JCE Streams: " + message + " : " + throwable);
- throwable.printStackTrace();
- }
- }
- /**
- * Test CipherInputStream in ENCRYPT_MODE, CipherOutputStream in DECRYPT_MODE
- */
- private void testReadWrite(String name, Key key, boolean authenticated, boolean useBc, boolean blocks)
- throws Exception
- {
- String lCode = "ABCDEFGHIJKLMNOPQRSTU";
- ByteArrayOutputStream bOut = new ByteArrayOutputStream();
- try
- {
- Cipher in = Cipher.getInstance(name, "BC");
- Cipher out = Cipher.getInstance(name, "BC");
- in.init(Cipher.ENCRYPT_MODE, key);
- if (in.getIV() != null)
- {
- out.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(in.getIV()));
- }
- else
- {
- out.init(Cipher.DECRYPT_MODE, key);
- }
- InputStream cIn = createInputStream(lCode.getBytes(), in, useBc);
- OutputStream cOut = createOutputStream(bOut, out, useBc);
- if (blocks)
- {
- byte[] block = new byte[in.getBlockSize() + 1];
- int c;
- while ((c = cIn.read(block)) >= 0)
- {
- cOut.write(block, 0, c);
- }
- }
- else
- {
- int c;
- while ((c = cIn.read()) >= 0)
- {
- cOut.write(c);
- }
- }
- cIn.close();
- cOut.flush();
- cOut.close();
- }
- catch (Exception e)
- {
- fail("Unexpected exception " + name, e, authenticated, useBc);
- }
- String res = new String(bOut.toByteArray());
- if (!res.equals(lCode))
- {
- fail("Failed - decrypted data doesn't match: " + name, authenticated, useBc);
- }
- }
- private static Key generateKey(String name)
- throws Exception
- {
- KeyGenerator kGen;
- if (name.indexOf('/') < 0)
- {
- kGen = KeyGenerator.getInstance(name, "BC");
- }
- else
- {
- kGen = KeyGenerator.getInstance(name.substring(0, name.indexOf('/')), "BC");
- }
- return kGen.generateKey();
- }
- public void performTest()
- throws Exception
- {
- int[] testSizes = new int[]{0, 1, 7, 8, 9, 15, 16, 17, 1023, 1024, 1025, 2047, 2048, 2049, 4095, 4096, 4097};
- for (int i = 0; i < testSizes.length; i++)
- {
- this.streamSize = testSizes[i];
- performTests();
- }
- }
- private void performTests()
- throws Exception
- {
- final String[] blockCiphers64 = new String[]{"BLOWFISH", "DES", "DESEDE", "TEA", "CAST5", "RC2", "XTEA"};
- for (int i = 0; i != blockCiphers64.length; i++)
- {
- testModes(blockCiphers64[i], new String[]{
- "/ECB/PKCS5Padding",
- "/CBC/PKCS5Padding",
- "/OFB/NoPadding",
- "/CFB/NoPadding",
- "/CTS/NoPadding",}, false);
- testModes(blockCiphers64[i], new String[]{"/EAX/NoPadding"}, true);
- }
- final String[] blockCiphers128 = new String[]{
- "AES",
- "NOEKEON",
- "Twofish",
- "CAST6",
- "SEED",
- "Serpent",
- "RC6",
- "CAMELLIA"};
- for (int i = 0; i != blockCiphers128.length; i++)
- {
- testModes(blockCiphers128[i], new String[]{
- "/ECB/PKCS5Padding",
- "/CBC/PKCS5Padding",
- "/OFB/NoPadding",
- "/CFB/NoPadding",
- "/CTS/NoPadding",
- "/CTR/NoPadding",
- "/SIC/NoPadding"}, false);
- testModes(blockCiphers128[i], new String[]{"/CCM/NoPadding", "/EAX/NoPadding", "/GCM/NoPadding", "/OCB/NoPadding"}, true);
- }
- final String[] streamCiphers = new String[]{
- "ARC4",
- "SALSA20",
- "XSalsa20",
- "ChaCha",
- "Grainv1",
- "Grain128",
- "HC128",
- "HC256"};
- for (int i = 0; i != streamCiphers.length; i++)
- {
- testModes(streamCiphers[i], new String[]{""}, false);
- }
- }
- public static void main(String[] args)
- {
- Security.addProvider(new BouncyCastleProvider());
- runTest(new CipherStreamTest2());
- }
- }