Changed to fixed-length frames (mostly untested).

This commit is contained in:
akwizgran
2012-08-28 09:15:04 +01:00
parent 312ad9d534
commit ff73905330
31 changed files with 448 additions and 892 deletions

View File

@@ -18,7 +18,6 @@
<test name='net.sf.briar.ProtocolIntegrationTest'/>
<test name='net.sf.briar.crypto.CounterModeTest'/>
<test name='net.sf.briar.crypto.ErasableKeyTest'/>
<test name='net.sf.briar.crypto.FramePeekingTest'/>
<test name='net.sf.briar.crypto.KeyDerivationTest'/>
<test name='net.sf.briar.db.BasicH2Test'/>
<test name='net.sf.briar.db.DatabaseCleanerImplTest'/>

View File

@@ -1,44 +0,0 @@
package net.sf.briar.crypto;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.util.ByteUtils;
import org.junit.Test;
public class FramePeekingTest extends BriarTestCase {
@Test
public void testFramePeeking() throws Exception {
CryptoComponent crypto = new CryptoComponentImpl();
ErasableKey key = crypto.generateTestKey();
Cipher frameCipher = crypto.getFrameCipher();
IvEncoder frameIvEncoder = crypto.getFrameIvEncoder();
byte[] iv = frameIvEncoder.encodeIv(ByteUtils.MAX_32_BIT_UNSIGNED);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
Cipher framePeekingCipher = crypto.getFramePeekingCipher();
IvEncoder framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
iv = framePeekingIvEncoder.encodeIv(ByteUtils.MAX_32_BIT_UNSIGNED);
ivSpec = new IvParameterSpec(iv);
framePeekingCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
// The ciphers should produce the same ciphertext, apart from the MAC
byte[] plaintext = new byte[123];
byte[] ciphertext = frameCipher.doFinal(plaintext);
byte[] peekingCiphertext = framePeekingCipher.doFinal(plaintext);
assertEquals(ciphertext.length, peekingCiphertext.length + MAC_LENGTH);
for(int i = 0; i < peekingCiphertext.length; i++) {
assertEquals(ciphertext[i], peekingCiphertext[i]);
}
}
}

View File

@@ -1,5 +1,9 @@
package net.sf.briar.protocol.simplex;
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.ByteArrayOutputStream;
import java.util.Collections;
import java.util.concurrent.Executor;
@@ -127,8 +131,9 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
will(returnValue(null));
}});
connection.write();
// Nothing should have been written
assertEquals(0, out.size());
// Nothing should have been written except the tag and an empty frame
int nothing = TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH;
assertEquals(nothing, out.size());
// The transport should have been disposed with exception == false
assertTrue(transport.getDisposed());
assertFalse(transport.getException());
@@ -178,7 +183,8 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
}});
connection.write();
// Something should have been written
assertTrue(out.size() > UniqueId.LENGTH + message.length);
int nothing = TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH;
assertTrue(out.size() > nothing + UniqueId.LENGTH + message.length);
// The transport should have been disposed with exception == false
assertTrue(transport.getDisposed());
assertFalse(transport.getException());

View File

@@ -1,182 +1,12 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.junit.Test;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.transport.ConnectionReader;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.junit.Test;
public class ConnectionReaderImplTest extends BriarTestCase {
private static final int MAX_PAYLOAD_LENGTH =
MAX_FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
public ConnectionReaderImplTest() throws Exception {
super();
}
// FIXME: Write tests
@Test
public void testLengthZero() throws Exception {
byte[] frame = new byte[HEADER_LENGTH + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, 0, 0, true);
// Read the frame
ByteArrayInputStream in = new ByteArrayInputStream(frame);
ConnectionReader r = createConnectionReader(in);
// There should be no bytes available before EOF
assertEquals(-1, r.getInputStream().read());
}
@Test
public void testLengthOne() throws Exception {
byte[] frame = new byte[HEADER_LENGTH + 1 + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, 1, 0, true);
// Read the frame
ByteArrayInputStream in = new ByteArrayInputStream(frame);
ConnectionReader r = createConnectionReader(in);
// There should be one byte available before EOF
assertEquals(0, r.getInputStream().read());
assertEquals(-1, r.getInputStream().read());
}
@Test
public void testMaxLength() throws Exception {
// First frame: max payload length
byte[] frame = new byte[MAX_FRAME_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, MAX_PAYLOAD_LENGTH, 0, false);
// Second frame: max payload length plus one
byte[] frame1 = new byte[MAX_FRAME_LENGTH + 1];
HeaderEncoder.encodeHeader(frame1, 1, MAX_PAYLOAD_LENGTH + 1, 0, false);
// Concatenate the frames
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(frame);
out.write(frame1);
// Read the first frame
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ConnectionReader r = createConnectionReader(in);
byte[] read = new byte[MAX_PAYLOAD_LENGTH];
TestUtils.readFully(r.getInputStream(), read);
// Try to read the second frame
byte[] read1 = new byte[MAX_PAYLOAD_LENGTH + 1];
try {
TestUtils.readFully(r.getInputStream(), read1);
fail();
} catch(FormatException expected) {}
}
@Test
public void testMaxLengthWithPadding() throws Exception {
int paddingLength = 10;
// First frame: max payload length, including padding
byte[] frame = new byte[MAX_FRAME_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, MAX_PAYLOAD_LENGTH - paddingLength,
paddingLength, false);
// Second frame: max payload length plus one, including padding
byte[] frame1 = new byte[MAX_FRAME_LENGTH + 1];
HeaderEncoder.encodeHeader(frame1, 1,
MAX_PAYLOAD_LENGTH + 1 - paddingLength, paddingLength, false);
// Concatenate the frames
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(frame);
out.write(frame1);
// Read the first frame
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ConnectionReader r = createConnectionReader(in);
byte[] read = new byte[MAX_PAYLOAD_LENGTH - paddingLength];
TestUtils.readFully(r.getInputStream(), read);
// Try to read the second frame
byte[] read1 = new byte[MAX_PAYLOAD_LENGTH + 1 - paddingLength];
try {
TestUtils.readFully(r.getInputStream(), read1);
fail();
} catch(FormatException expected) {}
}
@Test
public void testNonZeroPadding() throws Exception {
int payloadLength = 10, paddingLength = 10;
byte[] frame = new byte[HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, payloadLength, paddingLength,
false);
// Set a byte of the padding to a non-zero value
frame[HEADER_LENGTH + payloadLength] = 1;
// Read the frame
ByteArrayInputStream in = new ByteArrayInputStream(frame);
ConnectionReader r = createConnectionReader(in);
// The non-zero padding should be rejected
try {
r.getInputStream().read();
fail();
} catch(FormatException expected) {}
}
@Test
public void testMultipleFrames() throws Exception {
// First frame: 123-byte payload
int payloadLength = 123;
byte[] frame = new byte[HEADER_LENGTH + payloadLength + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false);
// Second frame: 1234-byte payload
int payloadLength1 = 1234;
byte[] frame1 = new byte[HEADER_LENGTH + payloadLength1 + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame1, 1, payloadLength1, 0, true);
// Concatenate the frames
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(frame);
out.write(frame1);
// Read the frames
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ConnectionReader r = createConnectionReader(in);
byte[] read = new byte[payloadLength];
TestUtils.readFully(r.getInputStream(), read);
assertArrayEquals(new byte[payloadLength], read);
byte[] read1 = new byte[payloadLength1];
TestUtils.readFully(r.getInputStream(), read1);
assertArrayEquals(new byte[payloadLength1], read1);
assertEquals(-1, r.getInputStream().read());
}
@Test
public void testLastFrameNotMarkedAsSuch() throws Exception {
// First frame: 123-byte payload
int payloadLength = 123;
byte[] frame = new byte[HEADER_LENGTH + payloadLength + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, payloadLength, 0, false);
// Second frame: 1234-byte payload
int payloadLength1 = 1234;
byte[] frame1 = new byte[HEADER_LENGTH + payloadLength1 + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame1, 1, payloadLength1, 0, false);
// Concatenate the frames
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(frame);
out.write(frame1);
// Read the frames
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ConnectionReader r = createConnectionReader(in);
byte[] read = new byte[payloadLength];
TestUtils.readFully(r.getInputStream(), read);
assertArrayEquals(new byte[payloadLength], read);
byte[] read1 = new byte[payloadLength1];
TestUtils.readFully(r.getInputStream(), read1);
assertArrayEquals(new byte[payloadLength1], read1);
try {
r.getInputStream().read();
fail();
} catch(FormatException expected) {}
}
private ConnectionReader createConnectionReader(InputStream in) {
FrameReader encryption = new NullIncomingEncryptionLayer(in);
return new ConnectionReaderImpl(encryption);
}
public void testNothing() {}
}

View File

@@ -1,102 +1,12 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.transport.ConnectionWriter;
import org.junit.Test;
import net.sf.briar.BriarTestCase;
public class ConnectionWriterImplTest extends BriarTestCase {
private static final int MAX_PAYLOAD_LENGTH =
MAX_FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
public ConnectionWriterImplTest() throws Exception {
super();
}
// FIXME: Write tests
@Test
public void testFlushWithoutWriteProducesNothing() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ConnectionWriter w = createConnectionWriter(out);
w.getOutputStream().flush();
w.getOutputStream().flush();
w.getOutputStream().flush();
assertEquals(0, out.size());
}
@Test
public void testSingleByteFrame() throws Exception {
// Create a single-byte frame
byte[] frame = new byte[HEADER_LENGTH + 1 + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, 1, 0, false);
// Check that the ConnectionWriter gets the same results
ByteArrayOutputStream out = new ByteArrayOutputStream();
ConnectionWriter w = createConnectionWriter(out);
w.getOutputStream().write(0);
w.getOutputStream().flush();
assertArrayEquals(frame, out.toByteArray());
}
@Test
public void testWriteByteToMaxLengthWritesFrame() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ConnectionWriter w = createConnectionWriter(out);
OutputStream out1 = w.getOutputStream();
// The first maxPayloadLength - 1 bytes should be buffered
for(int i = 0; i < MAX_PAYLOAD_LENGTH - 1; i++) out1.write(0);
assertEquals(0, out.size());
// The next byte should trigger the writing of a frame
out1.write(0);
assertEquals(MAX_FRAME_LENGTH, out.size());
}
@Test
public void testWriteArrayToMaxLengthWritesFrame() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ConnectionWriter w = createConnectionWriter(out);
OutputStream out1 = w.getOutputStream();
// The first maxPayloadLength - 1 bytes should be buffered
out1.write(new byte[MAX_PAYLOAD_LENGTH - 1]);
assertEquals(0, out.size());
// The next maxPayloadLength + 1 bytes should trigger two frames
out1.write(new byte[MAX_PAYLOAD_LENGTH + 1]);
assertEquals(MAX_FRAME_LENGTH * 2, out.size());
}
@Test
public void testMultipleFrames() throws Exception {
// First frame: 123-byte payload
byte[] frame = new byte[HEADER_LENGTH + 123 + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame, 0, 123, 0, false);
// Second frame: 1234-byte payload
byte[] frame1 = new byte[HEADER_LENGTH + 1234 + MAC_LENGTH];
HeaderEncoder.encodeHeader(frame1, 1, 1234, 0, false);
// Concatenate the frames
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(frame);
out.write(frame1);
byte[] expected = out.toByteArray();
// Check that the ConnectionWriter gets the same results
out.reset();
ConnectionWriter w = createConnectionWriter(out);
w.getOutputStream().write(new byte[123]);
w.getOutputStream().flush();
w.getOutputStream().write(new byte[1234]);
w.getOutputStream().flush();
byte[] actual = out.toByteArray();
assertArrayEquals(expected, actual);
}
private ConnectionWriter createConnectionWriter(OutputStream out) {
FrameWriter encryption = new NullOutgoingEncryptionLayer(out);
return new ConnectionWriterImpl(encryption);
}
public void testNothing() {}
}

View File

@@ -41,7 +41,7 @@ public class ConnectionWriterTest extends BriarTestCase {
}
@Test
public void testOverhead() throws Exception {
public void testOverheadWithTag() throws Exception {
ByteArrayOutputStream out =
new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
@@ -53,7 +53,26 @@ public class ConnectionWriterTest extends BriarTestCase {
// Check that there really is room for a packet
byte[] payload = new byte[MAX_PACKET_LENGTH];
w.getOutputStream().write(payload);
w.getOutputStream().flush();
w.getOutputStream().close();
long used = out.size();
assertTrue(used >= MAX_PACKET_LENGTH);
assertTrue(used <= MIN_CONNECTION_LENGTH);
}
@Test
public void testOverheadWithoutTag() throws Exception {
ByteArrayOutputStream out =
new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
MIN_CONNECTION_LENGTH, secret, false);
// Check that the connection writer thinks there's room for a packet
long capacity = w.getRemainingCapacity();
assertTrue(capacity >= MAX_PACKET_LENGTH);
assertTrue(capacity <= MIN_CONNECTION_LENGTH);
// Check that there really is room for a packet
byte[] payload = new byte[MAX_PACKET_LENGTH];
w.getOutputStream().write(payload);
w.getOutputStream().close();
long used = out.size();
assertTrue(used >= MAX_PACKET_LENGTH);
assertTrue(used <= MIN_CONNECTION_LENGTH);

View File

@@ -12,9 +12,9 @@ import java.util.Random;
import javax.crypto.Cipher;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.AuthenticatedCipher;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.crypto.CryptoModule;
@@ -26,9 +26,11 @@ import com.google.inject.Injector;
public class FrameReadWriteTest extends BriarTestCase {
private final int FRAME_LENGTH = 2048;
private final CryptoComponent crypto;
private final Cipher tagCipher, frameCipher, framePeekingCipher;
private final IvEncoder frameIvEncoder, framePeekingIvEncoder;
private final Cipher tagCipher;
private final AuthenticatedCipher frameCipher;
private final Random random;
private final byte[] outSecret;
private final ErasableKey tagKey, frameKey;
@@ -39,9 +41,6 @@ public class FrameReadWriteTest extends BriarTestCase {
crypto = i.getInstance(CryptoComponent.class);
tagCipher = crypto.getTagCipher();
frameCipher = crypto.getFrameCipher();
framePeekingCipher = crypto.getFramePeekingCipher();
frameIvEncoder = crypto.getFrameIvEncoder();
framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
random = new Random();
// Since we're sending frames to ourselves, we only need outgoing keys
outSecret = new byte[32];
@@ -65,7 +64,7 @@ public class FrameReadWriteTest extends BriarTestCase {
byte[] tag = new byte[TAG_LENGTH];
TagEncoder.encodeTag(tag, tagCipher, tagKey);
// Generate two random frames
byte[] frame = new byte[12345];
byte[] frame = new byte[1234];
random.nextBytes(frame);
byte[] frame1 = new byte[321];
random.nextBytes(frame1);
@@ -75,25 +74,23 @@ public class FrameReadWriteTest extends BriarTestCase {
// Write the frames
ByteArrayOutputStream out = new ByteArrayOutputStream();
FrameWriter encryptionOut = new OutgoingEncryptionLayer(out,
Long.MAX_VALUE, tagCipher, frameCipher, frameIvEncoder, tagCopy,
frameCopy);
ConnectionWriter writer = new ConnectionWriterImpl(encryptionOut);
Long.MAX_VALUE, tagCipher, frameCipher, tagCopy, frameCopy,
true, FRAME_LENGTH);
ConnectionWriter writer = new ConnectionWriterImpl(encryptionOut,
FRAME_LENGTH);
OutputStream out1 = writer.getOutputStream();
out1.write(frame);
out1.flush();
out1.write(frame1);
out1.flush();
// Read the tag back
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
byte[] recoveredTag = new byte[TAG_LENGTH];
assertEquals(TAG_LENGTH, in.read(recoveredTag));
assertArrayEquals(tag, recoveredTag);
assertTrue(TagEncoder.decodeTag(recoveredTag, tagCipher, tagKey));
// Read the frames back
FrameReader encryptionIn = new IncomingEncryptionLayer(in,
tagCipher, frameCipher, framePeekingCipher, frameIvEncoder,
framePeekingIvEncoder, tagKey, frameKey, false);
ConnectionReader reader = new ConnectionReaderImpl(encryptionIn);
byte[] output = out.toByteArray();
assertEquals(TAG_LENGTH + FRAME_LENGTH * 2, output.length);
// Read the tag and the frames back
ByteArrayInputStream in = new ByteArrayInputStream(output);
FrameReader encryptionIn = new IncomingEncryptionLayer(in, tagCipher,
frameCipher, tagKey, frameKey, true, FRAME_LENGTH);
ConnectionReader reader = new ConnectionReaderImpl(encryptionIn,
FRAME_LENGTH);
InputStream in1 = reader.getInputStream();
byte[] recovered = new byte[frame.length];
int offset = 0;

View File

@@ -1,148 +1,12 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.ByteArrayInputStream;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.crypto.CryptoModule;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class IncomingEncryptionLayerTest extends BriarTestCase {
private final Cipher tagCipher, frameCipher, framePeekingCipher;
private final IvEncoder frameIvEncoder, framePeekingIvEncoder;
private final ErasableKey tagKey, frameKey;
public IncomingEncryptionLayerTest() {
super();
Injector i = Guice.createInjector(new CryptoModule());
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
tagCipher = crypto.getTagCipher();
frameCipher = crypto.getFrameCipher();
framePeekingCipher = crypto.getFramePeekingCipher();
frameIvEncoder = crypto.getFrameIvEncoder();
framePeekingIvEncoder = crypto.getFramePeekingIvEncoder();
tagKey = crypto.generateTestKey();
frameKey = crypto.generateTestKey();
}
// FIXME: Write tests
@Test
public void testDecryptionWithTag() throws Exception {
// Calculate the tag
byte[] tag = new byte[TAG_LENGTH];
TagEncoder.encodeTag(tag, tagCipher, tagKey);
// Calculate the ciphertext for the first frame
byte[] plaintext = new byte[HEADER_LENGTH + 123];
HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0, false);
byte[] iv = frameIvEncoder.encodeIv(0L);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext = frameCipher.doFinal(plaintext);
// Calculate the ciphertext for the second frame
byte[] plaintext1 = new byte[HEADER_LENGTH + 1234];
HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0, false);
frameIvEncoder.updateIv(iv, 1L);
ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext1 = frameCipher.doFinal(plaintext1, 0,
plaintext1.length);
// Concatenate the ciphertexts, including the tag
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(tag);
out.write(ciphertext);
out.write(ciphertext1);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
// Use the encryption layer to decrypt the ciphertext
FrameReader decrypter = new IncomingEncryptionLayer(in, tagCipher,
frameCipher, framePeekingCipher, frameIvEncoder,
framePeekingIvEncoder, tagKey, frameKey, true);
// First frame
byte[] frame = new byte[MAX_FRAME_LENGTH];
assertTrue(decrypter.readFrame(frame));
assertEquals(0L, HeaderEncoder.getFrameNumber(frame));
int payload = HeaderEncoder.getPayloadLength(frame);
assertEquals(123, payload);
int padding = HeaderEncoder.getPaddingLength(frame);
assertEquals(0, padding);
assertEquals(plaintext.length, HEADER_LENGTH + payload + padding);
for(int i = 0; i < plaintext.length; i++) {
assertEquals(plaintext[i], frame[i]);
}
// Second frame
assertTrue(decrypter.readFrame(frame));
assertEquals(1L, HeaderEncoder.getFrameNumber(frame));
payload = HeaderEncoder.getPayloadLength(frame);
assertEquals(1234, payload);
padding = HeaderEncoder.getPaddingLength(frame);
assertEquals(0, padding);
assertEquals(plaintext1.length, HEADER_LENGTH + payload + padding);
for(int i = 0; i < plaintext1.length; i++) {
assertEquals(plaintext1[i], frame[i]);
}
}
@Test
public void testDecryptionWithoutTag() throws Exception {
// Calculate the ciphertext for the first frame
byte[] plaintext = new byte[HEADER_LENGTH + 123];
HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0, false);
byte[] iv = frameIvEncoder.encodeIv(0L);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext = frameCipher.doFinal(plaintext);
// Calculate the ciphertext for the second frame
byte[] plaintext1 = new byte[HEADER_LENGTH + 1234];
HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0, false);
frameIvEncoder.updateIv(iv, 1L);
ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext1 = frameCipher.doFinal(plaintext1, 0,
plaintext1.length);
// Concatenate the ciphertexts, excluding the tag
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(ciphertext);
out.write(ciphertext1);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
// Use the encryption layer to decrypt the ciphertext
FrameReader decrypter = new IncomingEncryptionLayer(in, tagCipher,
frameCipher, framePeekingCipher, frameIvEncoder,
framePeekingIvEncoder, tagKey, frameKey, false);
// First frame
byte[] frame = new byte[MAX_FRAME_LENGTH];
assertTrue(decrypter.readFrame(frame));
assertEquals(0L, HeaderEncoder.getFrameNumber(frame));
int payload = HeaderEncoder.getPayloadLength(frame);
assertEquals(123, payload);
int padding = HeaderEncoder.getPaddingLength(frame);
assertEquals(0, padding);
assertEquals(plaintext.length, HEADER_LENGTH + payload + padding);
for(int i = 0; i < plaintext.length; i++) {
assertEquals(plaintext[i], frame[i]);
}
// Second frame
assertTrue(decrypter.readFrame(frame));
assertEquals(1L, HeaderEncoder.getFrameNumber(frame));
payload = HeaderEncoder.getPayloadLength(frame);
assertEquals(1234, payload);
padding = HeaderEncoder.getPaddingLength(frame);
assertEquals(0, padding);
assertEquals(plaintext1.length, HEADER_LENGTH + payload + padding);
for(int i = 0; i < plaintext1.length; i++) {
assertEquals(plaintext1[i], frame[i]);
}
}
public void testNothing() {}
}

View File

@@ -19,28 +19,27 @@ class NullIncomingEncryptionLayer implements FrameReader {
this.in = in;
}
public boolean readFrame(byte[] frame) throws IOException {
// Read the frame header
int offset = 0, length = HEADER_LENGTH;
while(offset < length) {
int read = in.read(frame, offset, length - offset);
if(read == -1) {
if(offset == 0) return false;
throw new EOFException();
}
offset += read;
public int readFrame(byte[] frame) throws IOException {
// Read the frame
int ciphertextLength = 0;
while(ciphertextLength < MAX_FRAME_LENGTH) {
int read = in.read(frame, ciphertextLength,
MAX_FRAME_LENGTH - ciphertextLength);
if(read == -1) break; // We'll check the length later
ciphertextLength += read;
}
// Parse the frame header
int payload = HeaderEncoder.getPayloadLength(frame);
int padding = HeaderEncoder.getPaddingLength(frame);
length = HEADER_LENGTH + payload + padding + MAC_LENGTH;
if(length > MAX_FRAME_LENGTH) throw new FormatException();
// Read the remainder of the frame
while(offset < length) {
int read = in.read(frame, offset, length - offset);
if(read == -1) throw new EOFException();
offset += read;
}
return true;
int plaintextLength = ciphertextLength - MAC_LENGTH;
if(plaintextLength < HEADER_LENGTH) throw new EOFException();
// Decode and validate the header
boolean lastFrame = FrameEncoder.isLastFrame(frame);
if(!lastFrame && ciphertextLength < MAX_FRAME_LENGTH)
throw new EOFException();
int payloadLength = FrameEncoder.getPayloadLength(frame);
if(payloadLength > plaintextLength - HEADER_LENGTH)
throw new FormatException();
// If there's any padding it must be all zeroes
for(int i = HEADER_LENGTH + payloadLength; i < plaintextLength; i++)
if(frame[i] != 0) throw new FormatException();
return payloadLength;
}
}

View File

@@ -23,12 +23,18 @@ class NullOutgoingEncryptionLayer implements FrameWriter {
this.capacity = capacity;
}
public void writeFrame(byte[] frame) throws IOException {
int payload = HeaderEncoder.getPayloadLength(frame);
int padding = HeaderEncoder.getPaddingLength(frame);
int length = HEADER_LENGTH + payload + padding + MAC_LENGTH;
out.write(frame, 0, length);
capacity -= length;
public void writeFrame(byte[] frame, int payloadLength, int paddingLength,
boolean lastFrame) throws IOException {
int plaintextLength = HEADER_LENGTH + payloadLength + paddingLength;
int ciphertextLength = plaintextLength + MAC_LENGTH;
// Encode the header
FrameEncoder.encodeHeader(frame, lastFrame, payloadLength);
// If there's any padding it must all be zeroes
for(int i = HEADER_LENGTH + payloadLength; i < plaintextLength; i++)
frame[i] = 0;
// Write the frame
out.write(frame, 0, ciphertextLength);
capacity -= ciphertextLength;
}
public void flush() throws IOException {

View File

@@ -1,77 +1,12 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayOutputStream;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.crypto.IvEncoder;
import net.sf.briar.crypto.CryptoModule;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
import net.sf.briar.BriarTestCase;
public class OutgoingEncryptionLayerTest extends BriarTestCase {
private final Cipher tagCipher, frameCipher;
private final IvEncoder frameIvEncoder;
private final ErasableKey tagKey, frameKey;
public OutgoingEncryptionLayerTest() {
super();
Injector i = Guice.createInjector(new CryptoModule());
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
tagCipher = crypto.getTagCipher();
frameCipher = crypto.getFrameCipher();
frameIvEncoder = crypto.getFrameIvEncoder();
tagKey = crypto.generateTestKey();
frameKey = crypto.generateTestKey();
}
// FIXME: Write tests
@Test
public void testEncryptionWithTag() throws Exception {
// Calculate the expected tag
byte[] tag = new byte[TAG_LENGTH];
TagEncoder.encodeTag(tag, tagCipher, tagKey);
// Calculate the expected ciphertext for the first frame
byte[] iv = frameIvEncoder.encodeIv(0L);
byte[] plaintext = new byte[HEADER_LENGTH + 123];
HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0, false);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext = frameCipher.doFinal(plaintext);
// Calculate the expected ciphertext for the second frame
byte[] plaintext1 = new byte[HEADER_LENGTH + 1234];
HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0, true);
frameIvEncoder.updateIv(iv, 1L);
ivSpec = new IvParameterSpec(iv);
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
byte[] ciphertext1 = frameCipher.doFinal(plaintext1);
// Concatenate the ciphertexts
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(tag);
out.write(ciphertext);
out.write(ciphertext1);
byte[] expected = out.toByteArray();
// Use the encryption layer to encrypt the plaintext
out.reset();
FrameWriter encrypter = new OutgoingEncryptionLayer(out, Long.MAX_VALUE,
tagCipher, frameCipher, frameIvEncoder, tagKey, frameKey);
encrypter.writeFrame(plaintext);
encrypter.writeFrame(plaintext1);
byte[] actual = out.toByteArray();
// Check that the actual ciphertext matches the expected ciphertext
assertArrayEquals(expected, actual);
assertEquals(Long.MAX_VALUE - actual.length,
encrypter.getRemainingCapacity());
}
public void testNothing() {}
}