Changed the root package from net.sf.briar to org.briarproject.

This commit is contained in:
akwizgran
2014-01-08 16:18:30 +00:00
parent dce70f487c
commit 832476412c
427 changed files with 2507 additions and 2507 deletions

View File

@@ -0,0 +1,111 @@
package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import org.briarproject.BriarTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
public class ConnectionReaderImplTest extends BriarTestCase {
private static final int FRAME_LENGTH = 1024;
private static final int MAX_PAYLOAD_LENGTH =
FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
@Test
public void testEmptyFramesAreSkipped() throws Exception {
Mockery context = new Mockery();
final FrameReader reader = context.mock(FrameReader.class);
context.checking(new Expectations() {{
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(0)); // Empty frame
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(2)); // Non-empty frame with two payload bytes
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(0)); // Empty frame
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(-1)); // No more frames
}});
ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH);
assertEquals(0, c.read()); // Skip the first empty frame, read a byte
assertEquals(0, c.read()); // Read another byte
assertEquals(-1, c.read()); // Skip the second empty frame, reach EOF
assertEquals(-1, c.read()); // Still at EOF
context.assertIsSatisfied();
c.close();
}
@Test
public void testEmptyFramesAreSkippedWithBuffer() throws Exception {
Mockery context = new Mockery();
final FrameReader reader = context.mock(FrameReader.class);
context.checking(new Expectations() {{
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(0)); // Empty frame
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(2)); // Non-empty frame with two payload bytes
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(0)); // Empty frame
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(-1)); // No more frames
}});
ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH);
byte[] buf = new byte[MAX_PAYLOAD_LENGTH];
// Skip the first empty frame, read the two payload bytes
assertEquals(2, c.read(buf));
// Skip the second empty frame, reach EOF
assertEquals(-1, c.read(buf));
// Still at EOF
assertEquals(-1, c.read(buf));
context.assertIsSatisfied();
c.close();
}
@Test
public void testMultipleReadsPerFrame() throws Exception {
Mockery context = new Mockery();
final FrameReader reader = context.mock(FrameReader.class);
context.checking(new Expectations() {{
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(MAX_PAYLOAD_LENGTH)); // Nice long frame
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(-1)); // No more frames
}});
ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH);
byte[] buf = new byte[MAX_PAYLOAD_LENGTH / 2];
// Read the first half of the payload
assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf));
// Read the second half of the payload
assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf));
// Reach EOF
assertEquals(-1, c.read(buf, 0, buf.length));
context.assertIsSatisfied();
c.close();
}
@Test
public void testMultipleReadsPerFrameWithOffsets() throws Exception {
Mockery context = new Mockery();
final FrameReader reader = context.mock(FrameReader.class);
context.checking(new Expectations() {{
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(MAX_PAYLOAD_LENGTH)); // Nice long frame
oneOf(reader).readFrame(with(any(byte[].class)));
will(returnValue(-1)); // No more frames
}});
ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH);
byte[] buf = new byte[MAX_PAYLOAD_LENGTH];
// Read the first half of the payload
assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf, MAX_PAYLOAD_LENGTH / 2,
MAX_PAYLOAD_LENGTH / 2));
// Read the second half of the payload
assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf, 123,
MAX_PAYLOAD_LENGTH / 2));
// Reach EOF
assertEquals(-1, c.read(buf, 0, buf.length));
context.assertIsSatisfied();
c.close();
}
}

View File

@@ -0,0 +1,75 @@
package org.briarproject.transport;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.transport.ConnectionRegistry;
import org.junit.Test;
public class ConnectionRegistryImplTest extends BriarTestCase {
private final ContactId contactId, contactId1;
private final TransportId transportId, transportId1;
public ConnectionRegistryImplTest() {
contactId = new ContactId(1);
contactId1 = new ContactId(2);
transportId = new TransportId(TestUtils.getRandomId());
transportId1 = new TransportId(TestUtils.getRandomId());
}
@Test
public void testRegisterAndUnregister() {
ConnectionRegistry c = new ConnectionRegistryImpl();
// The registry should be empty
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Check that a registered connection shows up
c.registerConnection(contactId, transportId);
assertEquals(Arrays.asList(contactId),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Register an identical connection - lookup should be unaffected
c.registerConnection(contactId, transportId);
assertEquals(Arrays.asList(contactId),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Unregister one of the connections - lookup should be unaffected
c.unregisterConnection(contactId, transportId);
assertEquals(Arrays.asList(contactId),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Unregister the other connection - lookup should be affected
c.unregisterConnection(contactId, transportId);
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Try to unregister the connection again - exception should be thrown
try {
c.unregisterConnection(contactId, transportId);
fail();
} catch(IllegalArgumentException expected) {}
// Register both contacts with one transport, one contact with both
c.registerConnection(contactId, transportId);
c.registerConnection(contactId1, transportId);
c.registerConnection(contactId1, transportId1);
Collection<ContactId> connected = c.getConnectedContacts(transportId);
assertEquals(2, connected.size());
assertTrue(connected.contains(contactId));
assertTrue(connected.contains(contactId1));
assertEquals(Arrays.asList(contactId1),
c.getConnectedContacts(transportId1));
}
}

View File

@@ -0,0 +1,157 @@
package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.CONNECTION_WINDOW_SIZE;
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import static org.junit.Assert.assertArrayEquals;
import java.util.Collection;
import org.briarproject.BriarTestCase;
import org.junit.Test;
public class ConnectionWindowTest extends BriarTestCase {
@Test
public void testWindowSliding() {
ConnectionWindow w = new ConnectionWindow();
for(int i = 0; i < 100; i++) {
assertFalse(w.isSeen(i));
w.setSeen(i);
assertTrue(w.isSeen(i));
}
}
@Test
public void testWindowJumping() {
ConnectionWindow w = new ConnectionWindow();
for(int i = 0; i < 100; i += 13) {
assertFalse(w.isSeen(i));
w.setSeen(i);
assertTrue(w.isSeen(i));
}
}
@Test
public void testWindowUpperLimit() {
ConnectionWindow w = new ConnectionWindow();
// Centre is 0, highest value in window is 15
w.setSeen(15);
// Centre is 16, highest value in window is 31
w.setSeen(31);
try {
// Centre is 32, highest value in window is 47
w.setSeen(48);
fail();
} catch(IllegalArgumentException expected) {}
// Centre is max - 1, highest value in window is max
byte[] bitmap = new byte[CONNECTION_WINDOW_SIZE / 8];
w = new ConnectionWindow(MAX_32_BIT_UNSIGNED - 1, bitmap);
assertFalse(w.isSeen(MAX_32_BIT_UNSIGNED - 1));
assertFalse(w.isSeen(MAX_32_BIT_UNSIGNED));
// Values greater than max should never be allowed
try {
w.setSeen(MAX_32_BIT_UNSIGNED + 1);
fail();
} catch(IllegalArgumentException expected) {}
w.setSeen(MAX_32_BIT_UNSIGNED);
assertTrue(w.isSeen(MAX_32_BIT_UNSIGNED));
// Centre should have moved to max + 1
assertEquals(MAX_32_BIT_UNSIGNED + 1, w.getCentre());
// The bit corresponding to max should be set
byte[] expectedBitmap = new byte[CONNECTION_WINDOW_SIZE / 8];
expectedBitmap[expectedBitmap.length / 2 - 1] = 1; // 00000001
assertArrayEquals(expectedBitmap, w.getBitmap());
// Values greater than max should never be allowed even if centre > max
try {
w.setSeen(MAX_32_BIT_UNSIGNED + 1);
fail();
} catch(IllegalArgumentException expected) {}
}
@Test
public void testWindowLowerLimit() {
ConnectionWindow w = new ConnectionWindow();
// Centre is 0, negative values should never be allowed
try {
w.setSeen(-1);
fail();
} catch(IllegalArgumentException expected) {}
// Slide the window
w.setSeen(15);
// Centre is 16, lowest value in window is 0
w.setSeen(0);
// Slide the window
w.setSeen(16);
// Centre is 17, lowest value in window is 1
w.setSeen(1);
try {
w.setSeen(0);
fail();
} catch(IllegalArgumentException expected) {}
// Slide the window
w.setSeen(25);
// Centre is 26, lowest value in window is 10
w.setSeen(10);
try {
w.setSeen(9);
fail();
} catch(IllegalArgumentException expected) {}
// Centre should still be 26
assertEquals(26, w.getCentre());
// The bits corresponding to 10, 15, 16 and 25 should be set
byte[] expectedBitmap = new byte[CONNECTION_WINDOW_SIZE / 8];
expectedBitmap[0] = (byte) 134; // 10000110
expectedBitmap[1] = 1; // 00000001
assertArrayEquals(expectedBitmap, w.getBitmap());
}
@Test
public void testCannotSetSeenTwice() {
ConnectionWindow w = new ConnectionWindow();
w.setSeen(15);
try {
w.setSeen(15);
fail();
} catch(IllegalArgumentException expected) {}
}
@Test
public void testGetUnseenConnectionNumbers() {
ConnectionWindow w = new ConnectionWindow();
// Centre is 0; window should cover 0 to 15, inclusive, with none seen
Collection<Long> unseen = w.getUnseen();
assertEquals(16, unseen.size());
for(int i = 0; i < 16; i++) {
assertTrue(unseen.contains(Long.valueOf(i)));
assertFalse(w.isSeen(i));
}
w.setSeen(3);
w.setSeen(4);
// Centre is 5; window should cover 0 to 20, inclusive, with two seen
unseen = w.getUnseen();
assertEquals(19, unseen.size());
for(int i = 0; i < 21; i++) {
if(i == 3 || i == 4) {
assertFalse(unseen.contains(Long.valueOf(i)));
assertTrue(w.isSeen(i));
} else {
assertTrue(unseen.contains(Long.valueOf(i)));
assertFalse(w.isSeen(i));
}
}
w.setSeen(19);
// Centre is 20; window should cover 4 to 35, inclusive, with two seen
unseen = w.getUnseen();
assertEquals(30, unseen.size());
for(int i = 4; i < 36; i++) {
if(i == 4 || i == 19) {
assertFalse(unseen.contains(Long.valueOf(i)));
assertTrue(w.isSeen(i));
} else {
assertTrue(unseen.contains(Long.valueOf(i)));
assertFalse(w.isSeen(i));
}
}
}
}

View File

@@ -0,0 +1,123 @@
package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import org.briarproject.BriarTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
public class ConnectionWriterImplTest extends BriarTestCase {
private static final int FRAME_LENGTH = 1024;
private static final int MAX_PAYLOAD_LENGTH =
FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
@Test
public void testCloseWithoutWritingWritesFinalFrame() throws Exception {
Mockery context = new Mockery();
final FrameWriter writer = context.mock(FrameWriter.class);
context.checking(new Expectations() {{
// Write an empty final frame
oneOf(writer).writeFrame(with(any(byte[].class)), with(0),
with(true));
// Flush the stream
oneOf(writer).flush();
}});
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
c.close();
context.assertIsSatisfied();
}
@Test
public void testFlushWithoutBufferedDataWritesFrame() throws Exception {
Mockery context = new Mockery();
final FrameWriter writer = context.mock(FrameWriter.class);
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
context.checking(new Expectations() {{
// Flush the stream
oneOf(writer).flush();
}});
c.flush();
context.assertIsSatisfied();
}
@Test
public void testFlushWithBufferedDataWritesFrameAndFlushes()
throws Exception {
Mockery context = new Mockery();
final FrameWriter writer = context.mock(FrameWriter.class);
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
context.checking(new Expectations() {{
// Write a non-final frame with one payload byte
oneOf(writer).writeFrame(with(any(byte[].class)), with(1),
with(false));
// Flush the stream
oneOf(writer).flush();
}});
c.write(0);
c.flush();
context.assertIsSatisfied();
}
@Test
public void testSingleByteWritesWriteFullFrame() throws Exception {
Mockery context = new Mockery();
final FrameWriter writer = context.mock(FrameWriter.class);
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
context.checking(new Expectations() {{
// Write a full non-final frame
oneOf(writer).writeFrame(with(any(byte[].class)),
with(MAX_PAYLOAD_LENGTH), with(false));
}});
for(int i = 0; i < MAX_PAYLOAD_LENGTH; i++) {
c.write(0);
}
context.assertIsSatisfied();
}
@Test
public void testMultiByteWritesWriteFullFrames() throws Exception {
Mockery context = new Mockery();
final FrameWriter writer = context.mock(FrameWriter.class);
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
context.checking(new Expectations() {{
// Write two full non-final frames
exactly(2).of(writer).writeFrame(with(any(byte[].class)),
with(MAX_PAYLOAD_LENGTH), with(false));
}});
// Sanity check
assertEquals(0, MAX_PAYLOAD_LENGTH % 2);
// Write two full payloads using four multi-byte writes
byte[] b = new byte[MAX_PAYLOAD_LENGTH / 2];
c.write(b);
c.write(b);
c.write(b);
c.write(b);
context.assertIsSatisfied();
}
@Test
public void testLargeMultiByteWriteWritesFullFrames() throws Exception {
Mockery context = new Mockery();
final FrameWriter writer = context.mock(FrameWriter.class);
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
context.checking(new Expectations() {{
// Write two full non-final frames
exactly(2).of(writer).writeFrame(with(any(byte[].class)),
with(MAX_PAYLOAD_LENGTH), with(false));
// Write a final frame with a one-byte payload
oneOf(writer).writeFrame(with(any(byte[].class)), with(1),
with(true));
// Flush the stream
oneOf(writer).flush();
}});
// Write two full payloads using one large multi-byte write
byte[] b = new byte[MAX_PAYLOAD_LENGTH * 2 + 1];
c.write(b);
// There should be one byte left in the buffer
c.close();
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,184 @@
package org.briarproject.transport;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static org.briarproject.api.transport.TransportConstants.AAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import java.io.ByteArrayInputStream;
import org.briarproject.BriarTestCase;
import org.briarproject.TestLifecycleModule;
import org.briarproject.api.FormatException;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.crypto.CryptoModule;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class IncomingEncryptionLayerTest extends BriarTestCase {
// FIXME: This is an integration test, not a unit test
private static final int FRAME_LENGTH = 1024;
private static final int MAX_PAYLOAD_LENGTH =
FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
private final CryptoComponent crypto;
private final AuthenticatedCipher frameCipher;
private final SecretKey frameKey;
public IncomingEncryptionLayerTest() {
Injector i = Guice.createInjector(new CryptoModule(),
new TestLifecycleModule());
crypto = i.getInstance(CryptoComponent.class);
frameCipher = crypto.getFrameCipher();
frameKey = crypto.generateSecretKey();
}
@Test
public void testReadValidFrames() throws Exception {
// Generate two valid frames
byte[] frame = generateFrame(0, FRAME_LENGTH, 123, false, false);
byte[] frame1 = generateFrame(1, FRAME_LENGTH, 123, false, false);
// Concatenate the frames
byte[] valid = new byte[FRAME_LENGTH * 2];
System.arraycopy(frame, 0, valid, 0, FRAME_LENGTH);
System.arraycopy(frame1, 0, valid, FRAME_LENGTH, FRAME_LENGTH);
// Read the frames
ByteArrayInputStream in = new ByteArrayInputStream(valid);
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
frameKey, FRAME_LENGTH);
byte[] buf = new byte[FRAME_LENGTH - MAC_LENGTH];
assertEquals(123, i.readFrame(buf));
assertEquals(123, i.readFrame(buf));
}
@Test
public void testTruncatedFrameThrowsException() throws Exception {
// Generate a valid frame
byte[] frame = generateFrame(0, FRAME_LENGTH, 123, false, false);
// Chop off the last byte
byte[] truncated = new byte[FRAME_LENGTH - 1];
System.arraycopy(frame, 0, truncated, 0, FRAME_LENGTH - 1);
// Try to read the frame, which should fail due to truncation
ByteArrayInputStream in = new ByteArrayInputStream(truncated);
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
frameKey, FRAME_LENGTH);
try {
i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
fail();
} catch(FormatException expected) {}
}
@Test
public void testModifiedFrameThrowsException() throws Exception {
// Generate a valid frame
byte[] frame = generateFrame(0, FRAME_LENGTH, 123, false, false);
// Modify a randomly chosen byte of the frame
frame[(int) (Math.random() * FRAME_LENGTH)] ^= 1;
// Try to read the frame, which should fail due to modification
ByteArrayInputStream in = new ByteArrayInputStream(frame);
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
frameKey, FRAME_LENGTH);
try {
i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
fail();
} catch(FormatException expected) {}
}
@Test
public void testShortNonFinalFrameThrowsException() throws Exception {
// Generate a short non-final frame
byte[] frame = generateFrame(0, FRAME_LENGTH - 1, 123, false, false);
// Try to read the frame, which should fail due to invalid length
ByteArrayInputStream in = new ByteArrayInputStream(frame);
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
frameKey, FRAME_LENGTH);
try {
i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
fail();
} catch(FormatException expected) {}
}
@Test
public void testShortFinalFrameDoesNotThrowException() throws Exception {
// Generate a short final frame
byte[] frame = generateFrame(0, FRAME_LENGTH - 1, 123, true, false);
// Read the frame
ByteArrayInputStream in = new ByteArrayInputStream(frame);
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
frameKey, FRAME_LENGTH);
int length = i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
assertEquals(123, length);
}
@Test
public void testInvalidPayloadLengthThrowsException() throws Exception {
// Generate a frame with an invalid payload length
byte[] frame = generateFrame(0, FRAME_LENGTH, MAX_PAYLOAD_LENGTH + 1,
false, false);
// Try to read the frame, which should fail due to invalid length
ByteArrayInputStream in = new ByteArrayInputStream(frame);
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
frameKey, FRAME_LENGTH);
try {
i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
fail();
} catch(FormatException expected) {}
}
@Test
public void testNonZeroPaddingThrowsException() throws Exception {
// Generate a frame with bad padding
byte[] frame = generateFrame(0, FRAME_LENGTH, 123, false, true);
// Try to read the frame, which should fail due to bad padding
ByteArrayInputStream in = new ByteArrayInputStream(frame);
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
frameKey, FRAME_LENGTH);
try {
i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
fail();
} catch(FormatException expected) {}
}
@Test
public void testCannotReadBeyondFinalFrame() throws Exception {
// Generate a valid final frame and another valid final frame after it
byte[] frame = generateFrame(0, FRAME_LENGTH, MAX_PAYLOAD_LENGTH, true,
false);
byte[] frame1 = generateFrame(1, FRAME_LENGTH, 123, true, false);
// Concatenate the frames
byte[] extraFrame = new byte[FRAME_LENGTH * 2];
System.arraycopy(frame, 0, extraFrame, 0, FRAME_LENGTH);
System.arraycopy(frame1, 0, extraFrame, FRAME_LENGTH, FRAME_LENGTH);
// Read the final frame, which should first read the tag
ByteArrayInputStream in = new ByteArrayInputStream(extraFrame);
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
frameKey, FRAME_LENGTH);
byte[] buf = new byte[FRAME_LENGTH - MAC_LENGTH];
assertEquals(MAX_PAYLOAD_LENGTH, i.readFrame(buf));
// The frame after the final frame should not be read
assertEquals(-1, i.readFrame(buf));
}
private byte[] generateFrame(long frameNumber, int frameLength,
int payloadLength, boolean finalFrame, boolean badPadding)
throws Exception {
byte[] iv = new byte[IV_LENGTH], aad = new byte[AAD_LENGTH];
byte[] plaintext = new byte[frameLength - MAC_LENGTH];
byte[] ciphertext = new byte[frameLength];
FrameEncoder.encodeIv(iv, frameNumber);
FrameEncoder.encodeAad(aad, frameNumber, plaintext.length);
frameCipher.init(ENCRYPT_MODE, frameKey, iv, aad);
FrameEncoder.encodeHeader(plaintext, finalFrame, payloadLength);
if(badPadding) plaintext[HEADER_LENGTH + payloadLength] = 1;
frameCipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0);
return ciphertext;
}
}

View File

@@ -0,0 +1,598 @@
package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.junit.Assert.assertArrayEquals;
import java.util.Arrays;
import java.util.Collections;
import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.system.Clock;
import org.briarproject.api.system.Timer;
import org.briarproject.api.transport.ConnectionContext;
import org.briarproject.api.transport.ConnectionRecogniser;
import org.briarproject.api.transport.Endpoint;
import org.briarproject.api.transport.TemporarySecret;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
public class KeyManagerImplTest extends BriarTestCase {
private static final long EPOCH = 1000L * 1000L * 1000L * 1000L;
private static final long MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
private static final long ROTATION_PERIOD_LENGTH =
MAX_LATENCY + MAX_CLOCK_DIFFERENCE;
private final ContactId contactId;
private final TransportId transportId;
private final byte[] secret0, secret1, secret2, secret3, secret4;
private final byte[] initialSecret;
public KeyManagerImplTest() {
contactId = new ContactId(234);
transportId = new TransportId(TestUtils.getRandomId());
secret0 = new byte[32];
secret1 = new byte[32];
secret2 = new byte[32];
secret3 = new byte[32];
secret4 = new byte[32];
for(int i = 0; i < secret0.length; i++) secret0[i] = 1;
for(int i = 0; i < secret1.length; i++) secret1[i] = 2;
for(int i = 0; i < secret2.length; i++) secret2[i] = 3;
for(int i = 0; i < secret3.length; i++) secret3[i] = 4;
for(int i = 0; i < secret4.length; i++) secret4[i] = 5;
initialSecret = new byte[32];
for(int i = 0; i < initialSecret.length; i++) initialSecret[i] = 123;
}
@Test
public void testStartAndStop() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ConnectionRecogniser connectionRecogniser =
context.mock(ConnectionRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Collections.emptyList()));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.emptyMap()));
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(connectionRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testEndpointAdded() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ConnectionRecogniser connectionRecogniser =
context.mock(ConnectionRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The secrets for periods 0 - 2 should be derived
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Collections.emptyList()));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// endpointAdded() during rotation period 1
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(crypto).deriveNextSecret(initialSecret, 0);
will(returnValue(secret0.clone()));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1.clone()));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
// The secrets for periods 0 - 2 should be added to the recogniser
oneOf(connectionRecogniser).addSecret(s0);
oneOf(connectionRecogniser).addSecret(s1);
oneOf(connectionRecogniser).addSecret(s2);
// stop()
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(connectionRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testEndpointAddedAndGetConnectionContext() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ConnectionRecogniser connectionRecogniser =
context.mock(ConnectionRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The secrets for periods 0 - 2 should be derived
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Collections.emptyList()));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// endpointAdded() during rotation period 1
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(crypto).deriveNextSecret(initialSecret, 0);
will(returnValue(secret0.clone()));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1.clone()));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
// The secrets for periods 0 - 2 should be added to the recogniser
oneOf(connectionRecogniser).addSecret(s0);
oneOf(connectionRecogniser).addSecret(s1);
oneOf(connectionRecogniser).addSecret(s2);
// getConnectionContext()
oneOf(db).incrementConnectionCounter(contactId, transportId, 1);
will(returnValue(0L));
// stop()
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(connectionRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
ConnectionContext ctx =
keyManager.getConnectionContext(contactId, transportId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret1, ctx.getSecret());
assertEquals(0, ctx.getConnectionNumber());
assertEquals(true, ctx.getAlice());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testLoadSecretsAtEpoch() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ConnectionRecogniser connectionRecogniser =
context.mock(ConnectionRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The DB contains the secrets for periods 0 - 2
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Arrays.asList(s0, s1, s2)));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
// The current time is the epoch, the start of period 1
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
// The secrets for periods 0 - 2 should be added to the recogniser
oneOf(connectionRecogniser).addSecret(s0);
oneOf(connectionRecogniser).addSecret(s1);
oneOf(connectionRecogniser).addSecret(s2);
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(connectionRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testLoadSecretsAtStartOfPeriod2() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ConnectionRecogniser connectionRecogniser =
context.mock(ConnectionRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The DB contains the secrets for periods 0 - 2
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
// The secret for period 3 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Arrays.asList(s0, s1, s2)));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
// The current time is the start of period 2
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH + ROTATION_PERIOD_LENGTH));
// The secret for period 3 should be derived and stored
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1.clone()));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3.clone()));
oneOf(db).addSecrets(Arrays.asList(s3));
// The secrets for periods 1 - 3 should be added to the recogniser
oneOf(connectionRecogniser).addSecret(s1);
oneOf(connectionRecogniser).addSecret(s2);
oneOf(connectionRecogniser).addSecret(s3);
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(connectionRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testLoadSecretsAtEndOfPeriod3() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ConnectionRecogniser connectionRecogniser =
context.mock(ConnectionRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The DB contains the secrets for periods 0 - 2
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
// The secrets for periods 3 and 4 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Arrays.asList(s0, s1, s2)));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
// The current time is the end of period 3
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH + 3 * ROTATION_PERIOD_LENGTH - 1));
// The secrets for periods 3 and 4 should be derived from secret 1
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3.clone()));
oneOf(crypto).deriveNextSecret(secret3, 4);
will(returnValue(secret4.clone()));
// The new secrets should be stored
oneOf(db).addSecrets(Arrays.asList(s3, s4));
// The secrets for periods 2 - 4 should be added to the recogniser
oneOf(connectionRecogniser).addSecret(s2);
oneOf(connectionRecogniser).addSecret(s3);
oneOf(connectionRecogniser).addSecret(s4);
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(connectionRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testLoadSecretsAndRotateInSamePeriod() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ConnectionRecogniser connectionRecogniser =
context.mock(ConnectionRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The DB contains the secrets for periods 0 - 2
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Arrays.asList(s0, s1, s2)));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
// The current time is the epoch, the start of period 1
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
// The secrets for periods 0 - 2 should be added to the recogniser
oneOf(connectionRecogniser).addSecret(s0);
oneOf(connectionRecogniser).addSecret(s1);
oneOf(connectionRecogniser).addSecret(s2);
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// run() during period 1: the secrets should not be affected
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH + 1));
// getConnectionContext()
oneOf(db).incrementConnectionCounter(contactId, transportId, 1);
will(returnValue(0L));
// stop()
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(connectionRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.run();
ConnectionContext ctx =
keyManager.getConnectionContext(contactId, transportId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret1, ctx.getSecret());
assertEquals(0, ctx.getConnectionNumber());
assertEquals(true, ctx.getAlice());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testLoadSecretsAndRotateInNextPeriod() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ConnectionRecogniser connectionRecogniser =
context.mock(ConnectionRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The DB contains the secrets for periods 0 - 2
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
// The secret for period 3 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Arrays.asList(s0, s1, s2)));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
// The current time is the epoch, the start of period 1
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
// The secrets for periods 0 - 2 should be added to the recogniser
oneOf(connectionRecogniser).addSecret(s0);
oneOf(connectionRecogniser).addSecret(s1);
oneOf(connectionRecogniser).addSecret(s2);
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// run() during period 2: the secrets should be rotated
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH + ROTATION_PERIOD_LENGTH + 1));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1.clone()));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3.clone()));
oneOf(connectionRecogniser).removeSecret(contactId, transportId, 0);
oneOf(db).addSecrets(Arrays.asList(s3));
oneOf(connectionRecogniser).addSecret(s3);
// getConnectionContext()
oneOf(db).incrementConnectionCounter(contactId, transportId, 2);
will(returnValue(0L));
// stop()
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(connectionRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.run();
ConnectionContext ctx =
keyManager.getConnectionContext(contactId, transportId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret2, ctx.getSecret());
assertEquals(0, ctx.getConnectionNumber());
assertEquals(true, ctx.getAlice());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testLoadSecretsAndRotateAWholePeriodLate() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final ConnectionRecogniser connectionRecogniser =
context.mock(ConnectionRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The DB contains the secrets for periods 0 - 2
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
// The secrets for periods 3 and 4 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Arrays.asList(s0, s1, s2)));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
// The current time is the epoch, the start of period 1
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
// The secrets for periods 0 - 2 should be added to the recogniser
oneOf(connectionRecogniser).addSecret(s0);
oneOf(connectionRecogniser).addSecret(s1);
oneOf(connectionRecogniser).addSecret(s2);
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// run() during period 3 (late): the secrets should be rotated
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH + 2 * ROTATION_PERIOD_LENGTH + 1));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3.clone()));
oneOf(crypto).deriveNextSecret(secret3, 4);
will(returnValue(secret4.clone()));
oneOf(connectionRecogniser).removeSecret(contactId, transportId, 0);
oneOf(connectionRecogniser).removeSecret(contactId, transportId, 1);
oneOf(db).addSecrets(Arrays.asList(s3, s4));
oneOf(connectionRecogniser).addSecret(s3);
oneOf(connectionRecogniser).addSecret(s4);
// getConnectionContext()
oneOf(db).incrementConnectionCounter(contactId, transportId, 3);
will(returnValue(0L));
// stop()
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(connectionRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.run();
ConnectionContext ctx =
keyManager.getConnectionContext(contactId, transportId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret3, ctx.getSecret());
assertEquals(0, ctx.getConnectionNumber());
assertEquals(true, ctx.getAlice());
keyManager.stop();
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,894 @@
package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import java.util.Arrays;
import java.util.Collections;
import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.system.Clock;
import org.briarproject.api.system.Timer;
import org.briarproject.api.transport.ConnectionContext;
import org.briarproject.api.transport.ConnectionRecogniser;
import org.briarproject.api.transport.Endpoint;
import org.briarproject.api.transport.TemporarySecret;
import org.briarproject.util.ByteUtils;
import org.hamcrest.Description;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
import org.junit.Test;
public class KeyRotationIntegrationTest extends BriarTestCase {
private static final long EPOCH = 1000L * 1000L * 1000L * 1000L;
private static final long MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
private static final long ROTATION_PERIOD_LENGTH =
MAX_LATENCY + MAX_CLOCK_DIFFERENCE;
private final ContactId contactId;
private final TransportId transportId;
private final byte[] secret0, secret1, secret2, secret3, secret4;
private final byte[] key0, key1, key2, key3, key4;
private final byte[] initialSecret;
public KeyRotationIntegrationTest() {
contactId = new ContactId(234);
transportId = new TransportId(TestUtils.getRandomId());
secret0 = new byte[32];
secret1 = new byte[32];
secret2 = new byte[32];
secret3 = new byte[32];
secret4 = new byte[32];
for(int i = 0; i < secret0.length; i++) secret0[i] = 1;
for(int i = 0; i < secret1.length; i++) secret1[i] = 2;
for(int i = 0; i < secret2.length; i++) secret2[i] = 3;
for(int i = 0; i < secret3.length; i++) secret3[i] = 4;
for(int i = 0; i < secret4.length; i++) secret4[i] = 5;
key0 = new byte[32];
key1 = new byte[32];
key2 = new byte[32];
key3 = new byte[32];
key4 = new byte[32];
for(int i = 0; i < key0.length; i++) key0[i] = 1;
for(int i = 0; i < key1.length; i++) key1[i] = 2;
for(int i = 0; i < key2.length; i++) key2[i] = 3;
for(int i = 0; i < key3.length; i++) key3[i] = 4;
for(int i = 0; i < key4.length; i++) key4[i] = 5;
initialSecret = new byte[32];
for(int i = 0; i < initialSecret.length; i++) initialSecret[i] = 123;
}
@Test
public void testStartAndStop() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final ConnectionRecogniser connectionRecogniser =
new ConnectionRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Collections.emptyList()));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.emptyMap()));
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testEndpointAdded() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final SecretKey k0 = context.mock(SecretKey.class, "k0");
final SecretKey k1 = context.mock(SecretKey.class, "k1");
final SecretKey k2 = context.mock(SecretKey.class, "k2");
final ConnectionRecogniser connectionRecogniser =
new ConnectionRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The secrets for periods 0 - 2 should be derived
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Collections.emptyList()));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// endpointAdded() during rotation period 1
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(crypto).deriveNextSecret(initialSecret, 0);
will(returnValue(secret0.clone()));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1.clone()));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
// The recogniser should derive the tags for period 0
oneOf(crypto).deriveTagKey(secret0, false);
will(returnValue(k0));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
with((long) i));
will(new EncodeTagAction());
oneOf(k0).getEncoded();
will(returnValue(key0));
}
oneOf(k0).erase();
// The recogniser should derive the tags for period 1
oneOf(crypto).deriveTagKey(secret1, false);
will(returnValue(k1));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
with((long) i));
will(new EncodeTagAction());
oneOf(k1).getEncoded();
will(returnValue(key1));
}
oneOf(k1).erase();
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// stop()
// The recogniser should derive the tags for period 0
oneOf(crypto).deriveTagKey(secret0, false);
will(returnValue(k0));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
with((long) i));
will(new EncodeTagAction());
oneOf(k0).getEncoded();
will(returnValue(key0));
}
oneOf(k0).erase();
// The recogniser should derive the tags for period 1
oneOf(crypto).deriveTagKey(secret1, false);
will(returnValue(k1));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
with((long) i));
will(new EncodeTagAction());
oneOf(k1).getEncoded();
will(returnValue(key1));
}
oneOf(k1).erase();
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// Remove the listener and stop the timer
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testEndpointAddedAndGetConnectionContext() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final SecretKey k0 = context.mock(SecretKey.class, "k0");
final SecretKey k1 = context.mock(SecretKey.class, "k1");
final SecretKey k2 = context.mock(SecretKey.class, "k2");
final ConnectionRecogniser connectionRecogniser =
new ConnectionRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The secrets for periods 0 - 2 should be derived
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Collections.emptyList()));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// endpointAdded() during rotation period 1
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(crypto).deriveNextSecret(initialSecret, 0);
will(returnValue(secret0.clone()));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1.clone()));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
// The recogniser should derive the tags for period 0
oneOf(crypto).deriveTagKey(secret0, false);
will(returnValue(k0));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
with((long) i));
will(new EncodeTagAction());
oneOf(k0).getEncoded();
will(returnValue(key0));
}
oneOf(k0).erase();
// The recogniser should derive the tags for period 1
oneOf(crypto).deriveTagKey(secret1, false);
will(returnValue(k1));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
with((long) i));
will(new EncodeTagAction());
oneOf(k1).getEncoded();
will(returnValue(key1));
}
oneOf(k1).erase();
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// getConnectionContext()
oneOf(db).incrementConnectionCounter(contactId, transportId, 1);
will(returnValue(0L));
// stop()
// The recogniser should derive the tags for period 0
oneOf(crypto).deriveTagKey(secret0, false);
will(returnValue(k0));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
with((long) i));
will(new EncodeTagAction());
oneOf(k0).getEncoded();
will(returnValue(key0));
}
oneOf(k0).erase();
// The recogniser should derive the tags for period 1
oneOf(crypto).deriveTagKey(secret1, false);
will(returnValue(k1));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
with((long) i));
will(new EncodeTagAction());
oneOf(k1).getEncoded();
will(returnValue(key1));
}
oneOf(k1).erase();
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// Remove the listener and stop the timer
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
ConnectionContext ctx =
keyManager.getConnectionContext(contactId, transportId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret1, ctx.getSecret());
assertEquals(0, ctx.getConnectionNumber());
assertEquals(true, ctx.getAlice());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testEndpointAddedAndAcceptConnection() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final SecretKey k0 = context.mock(SecretKey.class, "k0");
final SecretKey k1 = context.mock(SecretKey.class, "k1");
final SecretKey k2 = context.mock(SecretKey.class, "k2");
final ConnectionRecogniser connectionRecogniser =
new ConnectionRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The secrets for periods 0 - 2 should be derived
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Collections.emptyList()));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// endpointAdded() during rotation period 1
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
oneOf(crypto).deriveNextSecret(initialSecret, 0);
will(returnValue(secret0.clone()));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1.clone()));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
// The recogniser should derive the tags for period 0
oneOf(crypto).deriveTagKey(secret0, false);
will(returnValue(k0));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
with((long) i));
will(new EncodeTagAction());
oneOf(k0).getEncoded();
will(returnValue(key0));
}
oneOf(k0).erase();
// The recogniser should derive the tags for period 1
oneOf(crypto).deriveTagKey(secret1, false);
will(returnValue(k1));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
with((long) i));
will(new EncodeTagAction());
oneOf(k1).getEncoded();
will(returnValue(key1));
}
oneOf(k1).erase();
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// acceptConnection()
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
oneOf(crypto).encodeTag(with(any(byte[].class)),
with(k2), with(16L));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
oneOf(db).setConnectionWindow(contactId, transportId, 2, 1,
new byte[] {0, 1, 0, 0});
oneOf(k2).erase();
// stop()
// The recogniser should derive the tags for period 0
oneOf(crypto).deriveTagKey(secret0, false);
will(returnValue(k0));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
with((long) i));
will(new EncodeTagAction());
oneOf(k0).getEncoded();
will(returnValue(key0));
}
oneOf(k0).erase();
// The recogniser should derive the tags for period 1
oneOf(crypto).deriveTagKey(secret1, false);
will(returnValue(k1));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
with((long) i));
will(new EncodeTagAction());
oneOf(k1).getEncoded();
will(returnValue(key1));
}
oneOf(k1).erase();
// The recogniser should derive the updated tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 1; i < 17; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// Remove the listener and stop the timer
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
// Recognise the tag for connection 0 in period 2
byte[] tag = new byte[TAG_LENGTH];
encodeTag(tag, key2, 0);
ConnectionContext ctx =
connectionRecogniser.acceptConnection(transportId, tag);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret2, ctx.getSecret());
assertEquals(0, ctx.getConnectionNumber());
assertEquals(true, ctx.getAlice());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testLoadSecretsAtEpoch() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final SecretKey k0 = context.mock(SecretKey.class, "k0");
final SecretKey k1 = context.mock(SecretKey.class, "k1");
final SecretKey k2 = context.mock(SecretKey.class, "k2");
final ConnectionRecogniser connectionRecogniser =
new ConnectionRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The DB contains the secrets for periods 0 - 2
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Arrays.asList(s0, s1, s2)));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
// The current time is the epoch, the start of period 1
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
// The recogniser should derive the tags for period 0
oneOf(crypto).deriveTagKey(secret0, false);
will(returnValue(k0));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
with((long) i));
will(new EncodeTagAction());
oneOf(k0).getEncoded();
will(returnValue(key0));
}
oneOf(k0).erase();
// The recogniser should derive the tags for period 1
oneOf(crypto).deriveTagKey(secret1, false);
will(returnValue(k1));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
with((long) i));
will(new EncodeTagAction());
oneOf(k1).getEncoded();
will(returnValue(key1));
}
oneOf(k1).erase();
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// Start the timer
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
// The recogniser should remove the tags for period 0
oneOf(crypto).deriveTagKey(secret0, false);
will(returnValue(k0));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
with((long) i));
will(new EncodeTagAction());
oneOf(k0).getEncoded();
will(returnValue(key0));
}
oneOf(k0).erase();
// The recogniser should derive the tags for period 1
oneOf(crypto).deriveTagKey(secret1, false);
will(returnValue(k1));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
with((long) i));
will(new EncodeTagAction());
oneOf(k1).getEncoded();
will(returnValue(key1));
}
oneOf(k1).erase();
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// Remove the listener and stop the timer
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testLoadSecretsAtStartOfPeriod2() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final SecretKey k1 = context.mock(SecretKey.class, "k1");
final SecretKey k2 = context.mock(SecretKey.class, "k2");
final SecretKey k3 = context.mock(SecretKey.class, "k3");
final ConnectionRecogniser connectionRecogniser =
new ConnectionRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The DB contains the secrets for periods 0 - 2
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
// The secret for period 3 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Arrays.asList(s0, s1, s2)));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
// The current time is the start of period 2
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH + ROTATION_PERIOD_LENGTH));
// The secret for period 3 should be derived and stored
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1.clone()));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3.clone()));
oneOf(db).addSecrets(Arrays.asList(s3));
// The recogniser should derive the tags for period 1
oneOf(crypto).deriveTagKey(secret1, false);
will(returnValue(k1));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
with((long) i));
will(new EncodeTagAction());
oneOf(k1).getEncoded();
will(returnValue(key1));
}
oneOf(k1).erase();
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// The recogniser should derive the tags for period 3
oneOf(crypto).deriveTagKey(secret3, false);
will(returnValue(k3));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
with((long) i));
will(new EncodeTagAction());
oneOf(k3).getEncoded();
will(returnValue(key3));
}
oneOf(k3).erase();
// Start the timer
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
// The recogniser should derive the tags for period 1
oneOf(crypto).deriveTagKey(secret1, false);
will(returnValue(k1));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
with((long) i));
will(new EncodeTagAction());
oneOf(k1).getEncoded();
will(returnValue(key1));
}
oneOf(k1).erase();
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// The recogniser should remove the tags for period 3
oneOf(crypto).deriveTagKey(secret3, false);
will(returnValue(k3));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
with((long) i));
will(new EncodeTagAction());
oneOf(k3).getEncoded();
will(returnValue(key3));
}
oneOf(k3).erase();
// Remove the listener and stop the timer
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.stop();
context.assertIsSatisfied();
}
@Test
public void testLoadSecretsAtEndOfPeriod3() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final SecretKey k2 = context.mock(SecretKey.class, "k2");
final SecretKey k3 = context.mock(SecretKey.class, "k3");
final SecretKey k4 = context.mock(SecretKey.class, "k4");
final ConnectionRecogniser connectionRecogniser =
new ConnectionRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
// The DB contains the secrets for periods 0 - 2
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
// The secrets for periods 3 and 4 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4.clone());
context.checking(new Expectations() {{
// start()
oneOf(db).addListener(with(any(EventListener.class)));
oneOf(db).getSecrets();
will(returnValue(Arrays.asList(s0, s1, s2)));
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
// The current time is the end of period 3
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH + 3 * ROTATION_PERIOD_LENGTH - 1));
// The secrets for periods 3 and 4 should be derived from secret 1
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2.clone()));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3.clone()));
oneOf(crypto).deriveNextSecret(secret3, 4);
will(returnValue(secret4.clone()));
// The new secrets should be stored
oneOf(db).addSecrets(Arrays.asList(s3, s4));
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// The recogniser should derive the tags for period 3
oneOf(crypto).deriveTagKey(secret3, false);
will(returnValue(k3));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
with((long) i));
will(new EncodeTagAction());
oneOf(k3).getEncoded();
will(returnValue(key3));
}
oneOf(k3).erase();
// The recogniser should derive the tags for period 4
oneOf(crypto).deriveTagKey(secret4, false);
will(returnValue(k4));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k4),
with((long) i));
will(new EncodeTagAction());
oneOf(k4).getEncoded();
will(returnValue(key4));
}
oneOf(k4).erase();
// Start the timer
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
// The recogniser should derive the tags for period 2
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with((long) i));
will(new EncodeTagAction());
oneOf(k2).getEncoded();
will(returnValue(key2));
}
oneOf(k2).erase();
// The recogniser should remove the tags for period 3
oneOf(crypto).deriveTagKey(secret3, false);
will(returnValue(k3));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
with((long) i));
will(new EncodeTagAction());
oneOf(k3).getEncoded();
will(returnValue(key3));
}
oneOf(k3).erase();
// The recogniser should derive the tags for period 4
oneOf(crypto).deriveTagKey(secret4, false);
will(returnValue(k4));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k4),
with((long) i));
will(new EncodeTagAction());
oneOf(k4).getEncoded();
will(returnValue(key4));
}
oneOf(k4).erase();
// Remove the listener and stop the timer
oneOf(db).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.stop();
context.assertIsSatisfied();
}
private void encodeTag(byte[] tag, byte[] rawKey, long connection) {
// Encode a fake tag based on the key and connection number
System.arraycopy(rawKey, 0, tag, 0, tag.length);
ByteUtils.writeUint32(connection, tag, 0);
}
private class EncodeTagAction implements Action {
public void describeTo(Description description) {
description.appendText("Encodes a tag");
}
public Object invoke(Invocation invocation) throws Throwable {
byte[] tag = (byte[]) invocation.getParameter(0);
SecretKey key = (SecretKey) invocation.getParameter(1);
long connection = (Long) invocation.getParameter(2);
encodeTag(tag, key.getEncoded(), connection);
return null;
}
}
}

View File

@@ -0,0 +1,160 @@
package org.briarproject.transport;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static org.briarproject.api.transport.TransportConstants.AAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import java.io.ByteArrayOutputStream;
import org.briarproject.BriarTestCase;
import org.briarproject.TestLifecycleModule;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.crypto.CryptoModule;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class OutgoingEncryptionLayerTest extends BriarTestCase {
// FIXME: This is an integration test, not a unit test
private static final int FRAME_LENGTH = 1024;
private static final int MAX_PAYLOAD_LENGTH =
FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
private final CryptoComponent crypto;
private final AuthenticatedCipher frameCipher;
private final byte[] tag;
public OutgoingEncryptionLayerTest() {
Injector i = Guice.createInjector(new CryptoModule(),
new TestLifecycleModule());
crypto = i.getInstance(CryptoComponent.class);
frameCipher = crypto.getFrameCipher();
tag = new byte[TAG_LENGTH];
}
@Test
public void testEncryption() throws Exception {
int payloadLength = 123;
byte[] iv = new byte[IV_LENGTH], aad = new byte[AAD_LENGTH];
byte[] plaintext = new byte[FRAME_LENGTH - MAC_LENGTH];
byte[] ciphertext = new byte[FRAME_LENGTH];
SecretKey frameKey = crypto.generateSecretKey();
// Calculate the expected ciphertext
FrameEncoder.encodeIv(iv, 0);
FrameEncoder.encodeAad(aad, 0, plaintext.length);
frameCipher.init(ENCRYPT_MODE, frameKey, iv, aad);
FrameEncoder.encodeHeader(plaintext, false, payloadLength);
frameCipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0);
// Check that the actual tag and ciphertext match what's expected
ByteArrayOutputStream out = new ByteArrayOutputStream();
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
10 * FRAME_LENGTH, frameCipher, frameKey, FRAME_LENGTH, tag);
o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], payloadLength, false);
byte[] actual = out.toByteArray();
assertEquals(TAG_LENGTH + FRAME_LENGTH, actual.length);
for(int i = 0; i < TAG_LENGTH; i++) assertEquals(tag[i], actual[i]);
for(int i = 0; i < FRAME_LENGTH; i++) {
assertEquals("" + i, ciphertext[i], actual[TAG_LENGTH + i]);
}
}
@Test
public void testInitiatorClosesConnectionWithoutWriting() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Initiator's constructor
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
FRAME_LENGTH, tag);
// Write an empty final frame without having written any other frames
o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true);
// Nothing should be written to the output stream
assertEquals(0, out.size());
}
@Test
public void testResponderClosesConnectionWithoutWriting() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Responder's constructor
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
FRAME_LENGTH);
// Write an empty final frame without having written any other frames
o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true);
// An empty final frame should be written to the output stream
assertEquals(HEADER_LENGTH + MAC_LENGTH, out.size());
}
@Test
public void testRemainingCapacityWithTag() throws Exception {
int MAX_PAYLOAD_LENGTH = FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Initiator's constructor
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
FRAME_LENGTH, tag);
// There should be space for nine full frames and one partial frame
byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH];
assertEquals(10 * MAX_PAYLOAD_LENGTH - TAG_LENGTH,
o.getRemainingCapacity());
// Write nine frames, each containing a partial payload
for(int i = 0; i < 9; i++) {
o.writeFrame(frame, 123, false);
assertEquals((9 - i) * MAX_PAYLOAD_LENGTH - TAG_LENGTH,
o.getRemainingCapacity());
}
// Write the final frame, which will not be padded
o.writeFrame(frame, 123, true);
int finalFrameLength = HEADER_LENGTH + 123 + MAC_LENGTH;
assertEquals(MAX_PAYLOAD_LENGTH - TAG_LENGTH - finalFrameLength,
o.getRemainingCapacity());
}
@Test
public void testRemainingCapacityWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Responder's constructor
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
FRAME_LENGTH);
// There should be space for ten full frames
assertEquals(10 * MAX_PAYLOAD_LENGTH, o.getRemainingCapacity());
// Write nine frames, each containing a partial payload
byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH];
for(int i = 0; i < 9; i++) {
o.writeFrame(frame, 123, false);
assertEquals((9 - i) * MAX_PAYLOAD_LENGTH,
o.getRemainingCapacity());
}
// Write the final frame, which will not be padded
o.writeFrame(frame, 123, true);
int finalFrameLength = HEADER_LENGTH + 123 + MAC_LENGTH;
assertEquals(MAX_PAYLOAD_LENGTH - finalFrameLength,
o.getRemainingCapacity());
}
@Test
public void testRemainingCapacityLimitedByFrameNumbers() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// The connection has plenty of space so we're limited by frame numbers
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
Long.MAX_VALUE, frameCipher, crypto.generateSecretKey(),
FRAME_LENGTH);
// There should be enough frame numbers for 2^32 frames
assertEquals((1L << 32) * MAX_PAYLOAD_LENGTH, o.getRemainingCapacity());
// Write a frame containing a partial payload
byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH];
o.writeFrame(frame, 123, false);
// There should be enough frame numbers for 2^32 - 1 frames
assertEquals(((1L << 32) - 1) * MAX_PAYLOAD_LENGTH,
o.getRemainingCapacity());
}
}

View File

@@ -0,0 +1,133 @@
package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import java.util.Random;
import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.transport.ConnectionContext;
import org.briarproject.api.transport.TemporarySecret;
import org.briarproject.util.ByteUtils;
import org.hamcrest.Description;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
import org.junit.Test;
public class TransportConnectionRecogniserTest extends BriarTestCase {
private final ContactId contactId = new ContactId(234);
private final TransportId transportId =
new TransportId(TestUtils.getRandomId());
@Test
public void testAddAndRemoveSecret() {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final byte[] secret = new byte[32];
new Random().nextBytes(secret);
final boolean alice = false;
final SecretKey tagKey = context.mock(SecretKey.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Add secret
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
with((long) i));
will(new EncodeTagAction());
}
oneOf(tagKey).erase();
// Remove secret
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
with((long) i));
will(new EncodeTagAction());
}
oneOf(tagKey).erase();
}});
TemporarySecret s = new TemporarySecret(contactId, transportId, 123,
alice, 0, secret, 0, 0, new byte[4]);
TransportConnectionRecogniser recogniser =
new TransportConnectionRecogniser(crypto, db, transportId);
recogniser.addSecret(s);
recogniser.removeSecret(contactId, 0);
context.assertIsSatisfied();
}
@Test
public void testAcceptConnection() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final byte[] secret = new byte[32];
new Random().nextBytes(secret);
final boolean alice = false;
final SecretKey tagKey = context.mock(SecretKey.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Add secret
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
for(int i = 0; i < 16; i++) {
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
with((long) i));
will(new EncodeTagAction());
}
oneOf(tagKey).erase();
// Accept connection 0
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
// The window should slide to include connection 16
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
with(16L));
will(new EncodeTagAction());
// The updated window should be stored
oneOf(db).setConnectionWindow(contactId, transportId, 0, 1,
new byte[] {0, 1, 0, 0});
oneOf(tagKey).erase();
// Accept connection again - no expectations
}});
TemporarySecret s = new TemporarySecret(contactId, transportId, 123,
alice, 0, secret, 0, 0, new byte[4]);
TransportConnectionRecogniser recogniser =
new TransportConnectionRecogniser(crypto, db, transportId);
recogniser.addSecret(s);
// Connection 0 should be expected
byte[] tag = new byte[TAG_LENGTH];
ConnectionContext ctx = recogniser.acceptConnection(tag);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret, ctx.getSecret());
assertEquals(0, ctx.getConnectionNumber());
assertEquals(alice, ctx.getAlice());
context.assertIsSatisfied();
}
private static class EncodeTagAction implements Action {
public void describeTo(Description description) {
description.appendText("Encodes a tag");
}
public Object invoke(Invocation invocation) throws Throwable {
byte[] tag = (byte[]) invocation.getParameter(0);
long connection = (Long) invocation.getParameter(2);
// Encode a fake tag based on the connection number
ByteUtils.writeUint32(connection, tag, 0);
return null;
}
}
}

View File

@@ -0,0 +1,169 @@
package org.briarproject.transport;
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MIN_CONNECTION_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;
import org.briarproject.BriarTestCase;
import org.briarproject.TestLifecycleModule;
import org.briarproject.TestUtils;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.transport.ConnectionContext;
import org.briarproject.api.transport.ConnectionWriter;
import org.briarproject.api.transport.ConnectionWriterFactory;
import org.briarproject.crypto.CryptoModule;
import org.junit.Test;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
public class TransportIntegrationTest extends BriarTestCase {
private final int FRAME_LENGTH = 2048;
private final CryptoComponent crypto;
private final ConnectionWriterFactory connectionWriterFactory;
private final ContactId contactId;
private final TransportId transportId;
private final AuthenticatedCipher frameCipher;
private final Random random;
private final byte[] secret;
private final SecretKey frameKey;
public TransportIntegrationTest() {
Module testModule = new AbstractModule() {
public void configure() {
bind(ConnectionWriterFactory.class).to(
ConnectionWriterFactoryImpl.class);
}
};
Injector i = Guice.createInjector(testModule, new CryptoModule(),
new TestLifecycleModule());
crypto = i.getInstance(CryptoComponent.class);
connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
contactId = new ContactId(234);
transportId = new TransportId(TestUtils.getRandomId());
frameCipher = crypto.getFrameCipher();
random = new Random();
// Since we're sending frames to ourselves, we only need outgoing keys
secret = new byte[32];
random.nextBytes(secret);
frameKey = crypto.deriveFrameKey(secret, 0, true, true);
}
@Test
public void testInitiatorWriteAndRead() throws Exception {
testWriteAndRead(true);
}
@Test
public void testResponderWriteAndRead() throws Exception {
testWriteAndRead(false);
}
private void testWriteAndRead(boolean initiator) throws Exception {
// Generate two random frames
byte[] frame = new byte[1234];
random.nextBytes(frame);
byte[] frame1 = new byte[321];
random.nextBytes(frame1);
// Copy the frame key - the copy will be erased
SecretKey frameCopy = frameKey.copy();
// Write the frames
ByteArrayOutputStream out = new ByteArrayOutputStream();
FrameWriter encryptionOut = new OutgoingEncryptionLayer(out,
Long.MAX_VALUE, frameCipher, frameCopy, FRAME_LENGTH);
ConnectionWriterImpl writer = new ConnectionWriterImpl(encryptionOut,
FRAME_LENGTH);
OutputStream out1 = writer.getOutputStream();
out1.write(frame);
out1.flush();
out1.write(frame1);
out1.flush();
byte[] output = out.toByteArray();
assertEquals(FRAME_LENGTH * 2, output.length);
// Read the tag and the frames back
ByteArrayInputStream in = new ByteArrayInputStream(output);
FrameReader encryptionIn = new IncomingEncryptionLayer(in, frameCipher,
frameKey, FRAME_LENGTH);
ConnectionReaderImpl reader = new ConnectionReaderImpl(encryptionIn,
FRAME_LENGTH);
InputStream in1 = reader.getInputStream();
byte[] recovered = new byte[frame.length];
int offset = 0;
while(offset < recovered.length) {
int read = in1.read(recovered, offset, recovered.length - offset);
if(read == -1) break;
offset += read;
}
assertEquals(recovered.length, offset);
assertArrayEquals(frame, recovered);
byte[] recovered1 = new byte[frame1.length];
offset = 0;
while(offset < recovered1.length) {
int read = in1.read(recovered1, offset, recovered1.length - offset);
if(read == -1) break;
offset += read;
}
assertEquals(recovered1.length, offset);
assertArrayEquals(frame1, recovered1);
writer.close();
reader.close();
}
@Test
public void testOverheadWithTag() throws Exception {
ByteArrayOutputStream out =
new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
secret, 0, true);
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
MAX_FRAME_LENGTH, MIN_CONNECTION_LENGTH, ctx, false, true);
// 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);
}
@Test
public void testOverheadWithoutTag() throws Exception {
ByteArrayOutputStream out =
new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
secret, 0, true);
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
MAX_FRAME_LENGTH, MIN_CONNECTION_LENGTH, ctx, false, 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);
}
}