package freenet.crypt;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Random;
import junit.framework.TestCase;
import freenet.support.api.Bucket;
import freenet.support.io.ArrayBucket;
import freenet.support.io.BucketTools;
import freenet.support.io.FileUtil;
import freenet.support.io.NoCloseProxyOutputStream;
public class AEADStreamsTest extends TestCase {
public void testSuccessfulRoundTrip() throws IOException {
Random random = new Random(0x96231307);
for(int i=0;i<10;i++) {
ArrayBucket input = new ArrayBucket();
BucketTools.fill(input, random, 65536);
checkSuccessfulRoundTrip(16, random, input, new ArrayBucket(), new ArrayBucket());
checkSuccessfulRoundTrip(24, random, input, new ArrayBucket(), new ArrayBucket());
checkSuccessfulRoundTrip(32, random, input, new ArrayBucket(), new ArrayBucket());
}
}
public void testCorruptedRoundTrip() throws IOException {
Random random = new Random(0x96231307); // Same seed as first test, intentionally.
for(int i=0;i<10;i++) {
ArrayBucket input = new ArrayBucket();
BucketTools.fill(input, random, 65536);
checkFailedCorruptedRoundTrip(16, random, input, new ArrayBucket(), new ArrayBucket());
checkFailedCorruptedRoundTrip(24, random, input, new ArrayBucket(), new ArrayBucket());
checkFailedCorruptedRoundTrip(32, random, input, new ArrayBucket(), new ArrayBucket());
}
}
public void testTruncatedReadsWritesRoundTrip() throws IOException {
Random random = new Random(0x49ee92f5);
ArrayBucket input = new ArrayBucket();
BucketTools.fill(input, random, 512*1024);
checkSuccessfulRoundTripRandomSplits(16, random, input, new ArrayBucket(), new ArrayBucket());
checkSuccessfulRoundTripRandomSplits(24, random, input, new ArrayBucket(), new ArrayBucket());
checkSuccessfulRoundTripRandomSplits(32, random, input, new ArrayBucket(), new ArrayBucket());
}
public void checkSuccessfulRoundTrip(int keysize, Random random, Bucket input, Bucket output, Bucket decoded) throws IOException {
byte[] key = new byte[keysize];
random.nextBytes(key);
OutputStream os = output.getOutputStream();
AEADOutputStream cos = AEADOutputStream.innerCreateAES(os, key, random);
BucketTools.copyTo(input, cos, -1);
cos.close();
assertTrue(output.size() > input.size());
InputStream is = output.getInputStream();
AEADInputStream cis = AEADInputStream.createAES(is, key);
BucketTools.copyFrom(decoded, cis, -1);
assertEquals(decoded.size(), input.size());
assertTrue(BucketTools.equalBuckets(decoded, input));
}
public void checkFailedCorruptedRoundTrip(int keysize, Random random, Bucket input, Bucket output, Bucket decoded) throws IOException {
byte[] key = new byte[keysize];
random.nextBytes(key);
OutputStream os = output.getOutputStream();
CorruptingOutputStream kos = new CorruptingOutputStream(os, 16L, input.size() + 16, 10, random);
AEADOutputStream cos = AEADOutputStream.innerCreateAES(kos, key, random);
BucketTools.copyTo(input, cos, -1);
cos.close();
assertTrue(output.size() > input.size());
InputStream is = output.getInputStream();
AEADInputStream cis = AEADInputStream.createAES(is, key);
try {
BucketTools.copyFrom(decoded, cis, -1);
cis.close();
fail("Checksum error should have been seen");
} catch (AEADVerificationFailedException e) {
// Expected.
}
assertEquals(decoded.size(), input.size());
assertFalse(BucketTools.equalBuckets(decoded, input));
}
public void checkSuccessfulRoundTripRandomSplits(int keysize, Random random, Bucket input, Bucket output, Bucket decoded) throws IOException {
byte[] key = new byte[keysize];
random.nextBytes(key);
OutputStream os = output.getOutputStream();
AEADOutputStream cos = AEADOutputStream.innerCreateAES(os, key, random);
BucketTools.copyTo(input, new RandomShortWriteOutputStream(cos, random), -1);
cos.close();
assertTrue(output.size() > input.size());
InputStream is = output.getInputStream();
AEADInputStream cis = AEADInputStream.createAES(is, key);
BucketTools.copyFrom(decoded, new RandomShortReadInputStream(cis, random), -1);
assertEquals(decoded.size(), input.size());
assertTrue(BucketTools.equalBuckets(decoded, input));
}
/** Check whether we can close the stream early.
* @throws IOException */
public void testCloseEarly() throws IOException {
ArrayBucket input = new ArrayBucket();
BucketTools.fill(input, 2048);
int keysize = 16;
Random random = new Random(0x47f6709f);
byte[] key = new byte[keysize];
random.nextBytes(key);
Bucket output = new ArrayBucket();
OutputStream os = output.getOutputStream();
AEADOutputStream cos = AEADOutputStream.innerCreateAES(os, key, random);
BucketTools.copyTo(input, cos, 2048);
cos.close();
InputStream is = output.getInputStream();
AEADInputStream cis = AEADInputStream.createAES(is, key);
byte[] first1KReadEncrypted = new byte[1024];
new DataInputStream(cis).readFully(first1KReadEncrypted);
byte[] first1KReadOriginal = new byte[1024];
new DataInputStream(input.getInputStream()).readFully(first1KReadOriginal);
assertTrue(Arrays.equals(first1KReadEncrypted, first1KReadOriginal));
cis.close();
}
/** If we close the stream early but there is garbage after that point, it should throw on
* close().
* @throws IOException */
public void testGarbageAfterClose() throws IOException {
ArrayBucket input = new ArrayBucket();
BucketTools.fill(input, 1024);
int keysize = 16;
Random random = new Random(0x47f6709f);
byte[] key = new byte[keysize];
random.nextBytes(key);
Bucket output = new ArrayBucket();
OutputStream os = output.getOutputStream();
AEADOutputStream cos = AEADOutputStream.innerCreateAES(new NoCloseProxyOutputStream(os), key, random);
BucketTools.copyTo(input, cos, -1);
cos.close();
// Now write garbage.
FileUtil.fill(os, 1024);
os.close();
InputStream is = output.getInputStream();
AEADInputStream cis = AEADInputStream.createAES(is, key);
byte[] first1KReadEncrypted = new byte[1024];
new DataInputStream(cis).readFully(first1KReadEncrypted);
byte[] first1KReadOriginal = new byte[1024];
new DataInputStream(input.getInputStream()).readFully(first1KReadOriginal);
assertTrue(Arrays.equals(first1KReadEncrypted, first1KReadOriginal));
try {
cis.close();
fail("Hash should be bogus due to garbage data at end");
} catch (AEADVerificationFailedException e) {
// Expected.
}
}
}