diff options
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.java | 514 |
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()); + } + +} |