Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'prov/src/test/java/org/spongycastle/jce/provider/test/CipherStreamTest2.java')
-rw-r--r--prov/src/test/java/org/spongycastle/jce/provider/test/CipherStreamTest2.java514
1 files changed, 514 insertions, 0 deletions
diff --git a/prov/src/test/java/org/spongycastle/jce/provider/test/CipherStreamTest2.java b/prov/src/test/java/org/spongycastle/jce/provider/test/CipherStreamTest2.java
new file mode 100644
index 00000000..7a9f443e
--- /dev/null
+++ b/prov/src/test/java/org/spongycastle/jce/provider/test/CipherStreamTest2.java
@@ -0,0 +1,514 @@
+package org.spongycastle.jce.provider.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.Security;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.spongycastle.crypto.io.InvalidCipherTextIOException;
+import org.spongycastle.jcajce.io.CipherInputStream;
+import org.spongycastle.jcajce.io.CipherOutputStream;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.test.SimpleTest;
+
+public class CipherStreamTest2
+ extends SimpleTest
+{
+ private int streamSize;
+
+ public String getName()
+ {
+ return "CipherStreamTest";
+ }
+
+ 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, "SC").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, "SC");
+ Cipher decrypt = Cipher.getInstance(name, "SC");
+ 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
+ }
+ 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, "SC");
+ Cipher decrypt = Cipher.getInstance(name, "SC");
+ 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 (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, "SC");
+ Cipher decrypt = Cipher.getInstance(name, "SC");
+ 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, "SC");
+ Cipher decrypt = Cipher.getInstance(name, "SC");
+ 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, "SC");
+ Cipher out = Cipher.getInstance(name, "SC");
+ 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, "SC");
+ }
+ else
+ {
+ kGen = KeyGenerator.getInstance(name.substring(0, name.indexOf('/')), "SC");
+ }
+ 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());
+ }
+
+}