Refactor KeyManager and TagRecogniser. #55

This commit is contained in:
akwizgran
2015-02-12 09:11:24 +00:00
parent 878a70620d
commit 9868feeb2a
60 changed files with 2123 additions and 3840 deletions

View File

@@ -1,600 +1,14 @@
package org.briarproject.transport;
import org.briarproject.BriarTestCase;
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.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.system.Clock;
import org.briarproject.api.system.Timer;
import org.briarproject.api.transport.Endpoint;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.TagRecogniser;
import org.briarproject.api.transport.TemporarySecret;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class KeyManagerImplTest extends BriarTestCase {
private static final long EPOCH = 1000L * 1000L * 1000L * 1000L;
private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
private static final int ROTATION_PERIOD =
MAX_CLOCK_DIFFERENCE + MAX_LATENCY;
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("id");
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 EventBus eventBus = context.mock(EventBus.class);
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, clock, timer);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(tagRecogniser).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 EventBus eventBus = context.mock(EventBus.class);
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
// The secrets for periods 0 - 2 should be added to the recogniser
oneOf(tagRecogniser).addSecret(s0);
oneOf(tagRecogniser).addSecret(s1);
oneOf(tagRecogniser).addSecret(s2);
// stop()
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(tagRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
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 EventBus eventBus = context.mock(EventBus.class);
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
// The secrets for periods 0 - 2 should be added to the recogniser
oneOf(tagRecogniser).addSecret(s0);
oneOf(tagRecogniser).addSecret(s1);
oneOf(tagRecogniser).addSecret(s2);
// getConnectionContext()
oneOf(db).incrementStreamCounter(contactId, transportId, 1);
will(returnValue(0L));
// stop()
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(tagRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
StreamContext ctx =
keyManager.getStreamContext(contactId, transportId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret1, ctx.getSecret());
assertEquals(0, ctx.getStreamNumber());
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 EventBus eventBus = context.mock(EventBus.class);
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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(tagRecogniser).addSecret(s0);
oneOf(tagRecogniser).addSecret(s1);
oneOf(tagRecogniser).addSecret(s2);
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(tagRecogniser).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 EventBus eventBus = context.mock(EventBus.class);
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
// The secret for period 3 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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));
// The secret for period 3 should be derived and stored
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3));
oneOf(db).addSecrets(Arrays.asList(s3));
// The secrets for periods 1 - 3 should be added to the recogniser
oneOf(tagRecogniser).addSecret(s1);
oneOf(tagRecogniser).addSecret(s2);
oneOf(tagRecogniser).addSecret(s3);
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(tagRecogniser).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 EventBus eventBus = context.mock(EventBus.class);
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
// The secrets for periods 3 and 4 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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 - 1));
// The secrets for periods 3 and 4 should be derived from secret 1
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3));
oneOf(crypto).deriveNextSecret(secret3, 4);
will(returnValue(secret4));
// 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(tagRecogniser).addSecret(s2);
oneOf(tagRecogniser).addSecret(s3);
oneOf(tagRecogniser).addSecret(s4);
oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(tagRecogniser).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 EventBus eventBus = context.mock(EventBus.class);
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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(tagRecogniser).addSecret(s0);
oneOf(tagRecogniser).addSecret(s1);
oneOf(tagRecogniser).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).incrementStreamCounter(contactId, transportId, 1);
will(returnValue(0L));
// stop()
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(tagRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.run();
StreamContext ctx =
keyManager.getStreamContext(contactId, transportId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret1, ctx.getSecret());
assertEquals(0, ctx.getStreamNumber());
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 EventBus eventBus = context.mock(EventBus.class);
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
// The secret for period 3 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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(tagRecogniser).addSecret(s0);
oneOf(tagRecogniser).addSecret(s1);
oneOf(tagRecogniser).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 + 1));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3));
oneOf(tagRecogniser).removeSecret(contactId, transportId, 0);
oneOf(db).addSecrets(Arrays.asList(s3));
oneOf(tagRecogniser).addSecret(s3);
// getConnectionContext()
oneOf(db).incrementStreamCounter(contactId, transportId, 2);
will(returnValue(0L));
// stop()
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(tagRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.run();
StreamContext ctx =
keyManager.getStreamContext(contactId, transportId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret2, ctx.getSecret());
assertEquals(0, ctx.getStreamNumber());
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 EventBus eventBus = context.mock(EventBus.class);
final TagRecogniser tagRecogniser = context.mock(TagRecogniser.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
// The secrets for periods 3 and 4 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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(tagRecogniser).addSecret(s0);
oneOf(tagRecogniser).addSecret(s1);
oneOf(tagRecogniser).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 + 1));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3));
oneOf(crypto).deriveNextSecret(secret3, 4);
will(returnValue(secret4));
oneOf(tagRecogniser).removeSecret(contactId, transportId, 0);
oneOf(tagRecogniser).removeSecret(contactId, transportId, 1);
oneOf(db).addSecrets(Arrays.asList(s3, s4));
oneOf(tagRecogniser).addSecret(s3);
oneOf(tagRecogniser).addSecret(s4);
// getConnectionContext()
oneOf(db).incrementStreamCounter(contactId, transportId, 3);
will(returnValue(0L));
// stop()
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
oneOf(tagRecogniser).removeSecrets();
}});
assertTrue(keyManager.start());
keyManager.run();
StreamContext ctx =
keyManager.getStreamContext(contactId, transportId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret3, ctx.getSecret());
assertEquals(0, ctx.getStreamNumber());
assertEquals(true, ctx.getAlice());
keyManager.stop();
context.assertIsSatisfied();
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -1,772 +0,0 @@
package org.briarproject.transport;
import org.briarproject.BriarTestCase;
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.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.system.Clock;
import org.briarproject.api.system.Timer;
import org.briarproject.api.transport.Endpoint;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.TagRecogniser;
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;
import java.util.Arrays;
import java.util.Collections;
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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class KeyRotationIntegrationTest extends BriarTestCase {
private static final long EPOCH = 1000L * 1000L * 1000L * 1000L;
private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
private static final int ROTATION_PERIOD =
MAX_CLOCK_DIFFERENCE + MAX_LATENCY;
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 SecretKey k0, k1, k2, k3, k4;
private final byte[] initialSecret;
public KeyRotationIntegrationTest() {
contactId = new ContactId(234);
transportId = new TransportId("id");
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];
k0 = new SecretKey(key0);
k1 = new SecretKey(key1);
k2 = new SecretKey(key2);
k3 = new SecretKey(key3);
k4 = new SecretKey(key4);
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 EventBus eventBus = context.mock(EventBus.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, clock, timer);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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(eventBus).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 EventBus eventBus = context.mock(EventBus.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
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());
}
// 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());
}
// 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());
}
// 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());
}
// 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());
}
// 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());
}
// Remove the listener and stop the timer
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
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 EventBus eventBus = context.mock(EventBus.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
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());
}
// 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());
}
// 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());
}
// getConnectionContext()
oneOf(db).incrementStreamCounter(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());
}
// 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());
}
// 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());
}
// Remove the listener and stop the timer
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
StreamContext ctx =
keyManager.getStreamContext(contactId, transportId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret1, ctx.getSecret());
assertEquals(0, ctx.getStreamNumber());
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 EventBus eventBus = context.mock(EventBus.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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));
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
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());
}
// 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());
}
// 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());
}
// acceptConnection()
oneOf(crypto).deriveTagKey(secret2, false);
will(returnValue(k2));
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
with(16L));
will(new EncodeTagAction());
oneOf(db).setReorderingWindow(contactId, transportId, 2, 1,
new byte[] {0, 1, 0, 0});
// 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());
}
// 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());
}
// 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());
}
// Remove the listener and stop the timer
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
// Recognise the tag for connection 0 in period 2
byte[] tag = new byte[TAG_LENGTH];
encodeTag(tag, key2, 0);
StreamContext ctx = tagRecogniser.recogniseTag(transportId, tag);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret2, ctx.getSecret());
assertEquals(0, ctx.getStreamNumber());
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 EventBus eventBus = context.mock(EventBus.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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());
}
// 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());
}
// 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());
}
// 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());
}
// 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());
}
// 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());
}
// Remove the listener and stop the timer
oneOf(eventBus).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 EventBus eventBus = context.mock(EventBus.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
// The secret for period 3 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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));
// The secret for period 3 should be derived and stored
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1));
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3));
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());
}
// 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());
}
// 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());
}
// 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());
}
// 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());
}
// 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());
}
// Remove the listener and stop the timer
oneOf(eventBus).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 EventBus eventBus = context.mock(EventBus.class);
final Clock clock = context.mock(Clock.class);
final Timer timer = context.mock(Timer.class);
final TagRecogniser tagRecogniser = new TagRecogniserImpl(crypto, db);
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
eventBus, tagRecogniser, 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);
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1);
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2);
// The secrets for periods 3 and 4 should be derived and stored
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3);
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4);
context.checking(new Expectations() {{
// start()
oneOf(eventBus).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 - 1));
// The secrets for periods 3 and 4 should be derived from secret 1
oneOf(crypto).deriveNextSecret(secret1, 2);
will(returnValue(secret2));
oneOf(crypto).deriveNextSecret(secret2, 3);
will(returnValue(secret3));
oneOf(crypto).deriveNextSecret(secret3, 4);
will(returnValue(secret4));
// 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());
}
// 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());
}
// 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());
}
// 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());
}
// 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());
}
// 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());
}
// Remove the listener and stop the timer
oneOf(eventBus).removeListener(with(any(EventListener.class)));
oneOf(timer).cancel();
}});
assertTrue(keyManager.start());
keyManager.stop();
context.assertIsSatisfied();
}
private void encodeTag(byte[] tag, byte[] rawKey, long streamNumber) {
// Encode a fake tag based on the key and stream number
System.arraycopy(rawKey, 0, tag, 0, tag.length);
ByteUtils.writeUint32(streamNumber, 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 streamNumber = (Long) invocation.getParameter(2);
encodeTag(tag, key.getBytes(), streamNumber);
return null;
}
}
}

View File

@@ -1,5 +1,10 @@
package org.briarproject.transport;
import org.briarproject.BriarTestCase;
import org.briarproject.api.transport.TransportConstants;
import org.briarproject.transport.ReorderingWindow.Change;
import org.junit.Assert;
import org.junit.Test;
import org.briarproject.BriarTestCase;
import org.junit.Test;
@@ -13,148 +18,102 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collections;
import java.util.Random;
import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.junit.Assert.assertArrayEquals;
public class ReorderingWindowTest extends BriarTestCase {
@Test
public void testWindowSliding() {
ReorderingWindow w = new ReorderingWindow();
for (int i = 0; i < 100; i++) {
assertFalse(w.isSeen(i));
w.setSeen(i);
assertTrue(w.isSeen(i));
}
}
@Test
public void testWindowJumping() {
ReorderingWindow w = new ReorderingWindow();
for (int i = 0; i < 100; i += 13) {
assertFalse(w.isSeen(i));
w.setSeen(i);
assertTrue(w.isSeen(i));
}
}
@Test
public void testWindowUpperLimit() {
ReorderingWindow w = new ReorderingWindow();
// 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
public void testBitmapConversion() {
Random random = new Random();
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
w = new ReorderingWindow(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[REORDERING_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) {}
for (int i = 0; i < 1000; i++) {
random.nextBytes(bitmap);
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
assertArrayEquals(bitmap, window.getBitmap());
}
}
@Test
public void testWindowLowerLimit() {
ReorderingWindow w = new ReorderingWindow();
// 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[REORDERING_WINDOW_SIZE / 8];
expectedBitmap[0] = (byte) 134; // 10000110
expectedBitmap[1] = 1; // 00000001
assertArrayEquals(expectedBitmap, w.getBitmap());
public void testWindowSlidesWhenFirstElementIsSeen() {
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
// Set the first element seen
Change change = window.setSeen(0L);
// The window should slide by one element
assertEquals(1L, window.getBase());
assertEquals(Collections.singletonList((long) REORDERING_WINDOW_SIZE), change.getAdded());
assertEquals(Collections.singletonList(0L), change.getRemoved());
// All elements in the window should be unseen
assertArrayEquals(bitmap, window.getBitmap());
}
@Test
public void testCannotSetSeenTwice() {
ReorderingWindow w = new ReorderingWindow();
w.setSeen(15);
try {
w.setSeen(15);
fail();
} catch (IllegalArgumentException expected) {}
public void testWindowDoesNotSlideWhenElementBelowMidpointIsSeen() {
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
// Set an element below the midpoint seen
Change change = window.setSeen(1L);
// The window should not slide
assertEquals(0L, window.getBase());
assertEquals(Collections.emptyList(), change.getAdded());
assertEquals(Collections.singletonList(1L), change.getRemoved());
// The second element in the window should be seen
bitmap[0] = 0x40; // 0100 0000
assertArrayEquals(bitmap, window.getBitmap());
}
@Test
public void testGetUnseenStreamNumbers() {
ReorderingWindow w = new ReorderingWindow();
// 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));
}
}
public void testWindowSlidesWhenElementAboveMidpointIsSeen() {
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
ReorderingWindow window = new ReorderingWindow(0, bitmap);
long aboveMidpoint = REORDERING_WINDOW_SIZE / 2;
// Set an element above the midpoint seen
Change change = window.setSeen(aboveMidpoint);
// The window should slide by one element
assertEquals(1L, window.getBase());
assertEquals(Collections.singletonList((long) REORDERING_WINDOW_SIZE), change.getAdded());
assertEquals(Arrays.asList(0L, aboveMidpoint), change.getRemoved());
// The highest element below the midpoint should be seen
bitmap[bitmap.length / 2 - 1] = (byte) 0x01; // 0000 0001
assertArrayEquals(bitmap, window.getBitmap());
}
@Test
public void testWindowSlidesUntilLowestElementIsUnseenWhenFirstElementIsSeen() {
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
window.setSeen(1L);
// Set the first element seen
Change change = window.setSeen(0L);
// The window should slide by two elements
assertEquals(2L, window.getBase());
assertEquals(Arrays.asList((long) REORDERING_WINDOW_SIZE,
(long) (REORDERING_WINDOW_SIZE + 1)), change.getAdded());
assertEquals(Collections.singletonList(0L), change.getRemoved());
// All elements in the window should be unseen
assertArrayEquals(bitmap, window.getBitmap());
}
@Test
public void testWindowSlidesUntilLowestElementIsUnseenWhenElementAboveMidpointIsSeen() {
byte[] bitmap = new byte[REORDERING_WINDOW_SIZE / 8];
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
window.setSeen(1L);
long aboveMidpoint = REORDERING_WINDOW_SIZE / 2;
// Set an element above the midpoint seen
Change change = window.setSeen(aboveMidpoint);
// The window should slide by two elements
assertEquals(2L, window.getBase());
assertEquals(Arrays.asList((long) REORDERING_WINDOW_SIZE,
(long) (REORDERING_WINDOW_SIZE + 1)), change.getAdded());
assertEquals(Arrays.asList(0L, aboveMidpoint), change.getRemoved());
// The second-highest element below the midpoint should be seen
bitmap[bitmap.length / 2 - 1] = (byte) 0x02; // 0000 0010
assertArrayEquals(bitmap, window.getBitmap());
}
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.transport;
import org.briarproject.BriarTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class TransportKeyManagerTest extends BriarTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -1,130 +0,0 @@
package org.briarproject.transport;
import org.briarproject.BriarTestCase;
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.StreamContext;
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;
import java.util.Random;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class TransportTagRecogniserTest extends BriarTestCase {
private final ContactId contactId = new ContactId(234);
private final TransportId transportId = new TransportId("id");
private final SecretKey tagKey = new SecretKey(new byte[32]);
@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 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());
}
// 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());
}
}});
TemporarySecret s = new TemporarySecret(contactId, transportId, 123,
alice, 0, secret, 0, 0, new byte[4]);
TransportTagRecogniser recogniser =
new TransportTagRecogniser(crypto, db, transportId);
recogniser.addSecret(s);
recogniser.removeSecret(contactId, 0);
context.assertIsSatisfied();
}
@Test
public void testRecogniseTag() 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 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());
}
// Recognise tag 0
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
// The window should slide to include tag 16
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
with(16L));
will(new EncodeTagAction());
// The updated window should be stored
oneOf(db).setReorderingWindow(contactId, transportId, 0, 1,
new byte[] {0, 1, 0, 0});
// Recognise tag again - no expectations
}});
TemporarySecret s = new TemporarySecret(contactId, transportId, 123,
alice, 0, secret, 0, 0, new byte[4]);
TransportTagRecogniser recogniser =
new TransportTagRecogniser(crypto, db, transportId);
recogniser.addSecret(s);
// Tag 0 should be expected
byte[] tag = new byte[TAG_LENGTH];
StreamContext ctx = recogniser.recogniseTag(tag);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertArrayEquals(secret, ctx.getSecret());
assertEquals(0, ctx.getStreamNumber());
assertEquals(alice, ctx.getAlice());
// Tag 0 should not be expected again
assertNull(recogniser.recogniseTag(tag));
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 streamNumber = (Long) invocation.getParameter(2);
// Encode a fake tag based on the stream number
ByteUtils.writeUint32(streamNumber, tag, 0);
return null;
}
}
}