diff --git a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
index 575244fab..9b43ebd1c 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
@@ -135,6 +135,7 @@ abstract class DuplexConnection implements DatabaseListener {
public void eventOccurred(DatabaseEvent e) {
if(e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
+ // FIXME: Listeners should not block
if(contactId.equals(c.getContactId())) dispose(false, true);
} else if(e instanceof GroupMessageAddedEvent) {
if(canSendOffer.getAndSet(false))
diff --git a/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java b/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java
index 6ab4d1730..409ca79fa 100644
--- a/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java
+++ b/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java
@@ -300,6 +300,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
if(e instanceof ContactRemovedEvent) {
ContactId c = ((ContactRemovedEvent) e).getContactId();
connectionRecogniser.removeSecrets(c);
+ // FIXME: Listeners should not block
synchronized(this) {
removeAndEraseSecrets(c, oldSecrets);
removeAndEraseSecrets(c, currentSecrets);
@@ -307,12 +308,14 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener {
}
} else if(e instanceof TransportAddedEvent) {
TransportAddedEvent t = (TransportAddedEvent) e;
+ // FIXME: Listeners should not block
synchronized(this) {
maxLatencies.put(t.getTransportId(), t.getMaxLatency());
}
} else if(e instanceof TransportRemovedEvent) {
TransportId t = ((TransportRemovedEvent) e).getTransportId();
connectionRecogniser.removeSecrets(t);
+ // FIXME: Listeners should not block
synchronized(this) {
maxLatencies.remove(t);
removeAndEraseSecrets(t, oldSecrets);
diff --git a/briar-core/src/net/sf/briar/transport/TransportConnectionRecogniser.java b/briar-core/src/net/sf/briar/transport/TransportConnectionRecogniser.java
index 93de724c6..a827641d8 100644
--- a/briar-core/src/net/sf/briar/transport/TransportConnectionRecogniser.java
+++ b/briar-core/src/net/sf/briar/transport/TransportConnectionRecogniser.java
@@ -20,6 +20,7 @@ import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.TemporarySecret;
import net.sf.briar.util.ByteUtils;
+// FIXME: Don't make alien calls with a lock held
/** A connection recogniser for a specific transport. */
class TransportConnectionRecogniser {
@@ -56,15 +57,15 @@ class TransportConnectionRecogniser {
byte[] tag1 = new byte[TAG_LENGTH];
crypto.encodeTag(tag1, cipher, key, connection1);
if(connection1 < connection) {
- TagContext old = tagMap.remove(new Bytes(tag1));
- assert old != null;
- ByteUtils.erase(old.context.getSecret());
+ TagContext removed = tagMap.remove(new Bytes(tag1));
+ assert removed != null;
+ ByteUtils.erase(removed.context.getSecret());
} else {
ConnectionContext ctx1 = new ConnectionContext(contactId,
transportId, secret.clone(), connection1, alice);
TagContext tctx1 = new TagContext(window, ctx1, period);
- TagContext old = tagMap.put(new Bytes(tag1), tctx1);
- assert old == null;
+ TagContext duplicate = tagMap.put(new Bytes(tag1), tctx1);
+ assert duplicate == null;
}
}
key.erase();
@@ -92,8 +93,8 @@ class TransportConnectionRecogniser {
ConnectionContext ctx = new ConnectionContext(contactId,
transportId, secret.clone(), connection, alice);
TagContext tctx = new TagContext(window, ctx, period);
- TagContext old = tagMap.put(new Bytes(tag), tctx);
- assert old == null;
+ TagContext duplicate = tagMap.put(new Bytes(tag), tctx);
+ assert duplicate == null;
}
key.erase();
// Create a removal context to remove the window later
@@ -116,9 +117,9 @@ class TransportConnectionRecogniser {
byte[] tag = new byte[TAG_LENGTH];
for(long connection : rctx.window.getUnseen()) {
crypto.encodeTag(tag, cipher, key, connection);
- TagContext old = tagMap.remove(new Bytes(tag));
- assert old != null;
- ByteUtils.erase(old.context.getSecret());
+ TagContext removed = tagMap.remove(new Bytes(tag));
+ assert removed != null;
+ ByteUtils.erase(removed.context.getSecret());
}
key.erase();
ByteUtils.erase(rctx.secret);
@@ -170,8 +171,8 @@ class TransportConnectionRecogniser {
@Override
public boolean equals(Object o) {
if(o instanceof RemovalKey) {
- RemovalKey w = (RemovalKey) o;
- return contactId.equals(w.contactId) && period == w.period;
+ RemovalKey k = (RemovalKey) o;
+ return contactId.equals(k.contactId) && period == k.period;
}
return false;
}
diff --git a/briar-tests/build.xml b/briar-tests/build.xml
index 8ed3ed747..cf248ed5b 100644
--- a/briar-tests/build.xml
+++ b/briar-tests/build.xml
@@ -104,6 +104,7 @@
+
diff --git a/briar-tests/src/net/sf/briar/transport/KeyManagerImplTest.java b/briar-tests/src/net/sf/briar/transport/KeyManagerImplTest.java
index 1e6bf41c9..93c05d567 100644
--- a/briar-tests/src/net/sf/briar/transport/KeyManagerImplTest.java
+++ b/briar-tests/src/net/sf/briar/transport/KeyManagerImplTest.java
@@ -1,10 +1,10 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
+import static org.junit.Assert.assertArrayEquals;
import java.util.Arrays;
import java.util.Collections;
-import java.util.TimerTask;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
@@ -15,6 +15,7 @@ import net.sf.briar.api.clock.Timer;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.event.DatabaseListener;
+import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionRecogniser;
import net.sf.briar.api.transport.Endpoint;
import net.sf.briar.api.transport.TemporarySecret;
@@ -33,6 +34,7 @@ public class KeyManagerImplTest extends BriarTestCase {
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);
@@ -47,6 +49,8 @@ public class KeyManagerImplTest extends BriarTestCase {
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
@@ -71,7 +75,7 @@ public class KeyManagerImplTest extends BriarTestCase {
will(returnValue(Collections.emptyMap()));
oneOf(clock).currentTimeMillis();
will(returnValue(EPOCH));
- oneOf(timer).scheduleAtFixedRate(with(any(TimerTask.class)),
+ oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(db).removeListener(with(any(DatabaseListener.class)));
@@ -85,6 +89,133 @@ public class KeyManagerImplTest extends BriarTestCase {
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(DatabaseListener.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(DatabaseListener.class)));
+ oneOf(timer).cancel();
+ oneOf(connectionRecogniser).removeSecrets();
+ }});
+
+ assertTrue(keyManager.start());
+ keyManager.endpointAdded(ep, 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(DatabaseListener.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(DatabaseListener.class)));
+ oneOf(timer).cancel();
+ oneOf(connectionRecogniser).removeSecrets();
+ }});
+
+ assertTrue(keyManager.start());
+ keyManager.endpointAdded(ep, 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();
@@ -98,7 +229,7 @@ public class KeyManagerImplTest extends BriarTestCase {
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
- // The DB contains secrets for periods 0 - 2
+ // 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());
@@ -119,7 +250,7 @@ public class KeyManagerImplTest extends BriarTestCase {
oneOf(connectionRecogniser).addSecret(s0);
oneOf(connectionRecogniser).addSecret(s1);
oneOf(connectionRecogniser).addSecret(s2);
- oneOf(timer).scheduleAtFixedRate(with(any(TimerTask.class)),
+ oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(db).removeListener(with(any(DatabaseListener.class)));
@@ -146,7 +277,7 @@ public class KeyManagerImplTest extends BriarTestCase {
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
- // The DB contains secrets for periods 0 - 2
+ // 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());
@@ -177,7 +308,7 @@ public class KeyManagerImplTest extends BriarTestCase {
oneOf(connectionRecogniser).addSecret(s1);
oneOf(connectionRecogniser).addSecret(s2);
oneOf(connectionRecogniser).addSecret(s3);
- oneOf(timer).scheduleAtFixedRate(with(any(TimerTask.class)),
+ oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(db).removeListener(with(any(DatabaseListener.class)));
@@ -192,7 +323,7 @@ public class KeyManagerImplTest extends BriarTestCase {
}
@Test
- public void testLoadSecretsAtStartOfPeriod3() throws Exception {
+ public void testLoadSecretsAtEndOfPeriod3() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
@@ -204,7 +335,7 @@ public class KeyManagerImplTest extends BriarTestCase {
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
connectionRecogniser, clock, timer);
- // The DB contains secrets for periods 0 - 2
+ // 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());
@@ -221,9 +352,9 @@ public class KeyManagerImplTest extends BriarTestCase {
oneOf(db).getTransportLatencies();
will(returnValue(Collections.singletonMap(transportId,
MAX_LATENCY)));
- // The current time is the start of period 3
+ // The current time is the end of period 3
oneOf(clock).currentTimeMillis();
- will(returnValue(EPOCH + 2 * ROTATION_PERIOD_LENGTH));
+ will(returnValue(EPOCH + 3 * ROTATION_PERIOD_LENGTH - 1));
// The secrets for periods 3 and 4 should be derived from secret 0
oneOf(crypto).deriveNextSecret(secret0, 1);
will(returnValue(secret1.clone()));
@@ -246,7 +377,7 @@ public class KeyManagerImplTest extends BriarTestCase {
oneOf(connectionRecogniser).addSecret(s2);
oneOf(connectionRecogniser).addSecret(s3);
oneOf(connectionRecogniser).addSecret(s4);
- oneOf(timer).scheduleAtFixedRate(with(any(TimerTask.class)),
+ oneOf(timer).scheduleAtFixedRate(with(keyManager),
with(any(long.class)), with(any(long.class)));
// stop()
oneOf(db).removeListener(with(any(DatabaseListener.class)));
@@ -259,4 +390,226 @@ public class KeyManagerImplTest extends BriarTestCase {
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(DatabaseListener.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(DatabaseListener.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(DatabaseListener.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(DatabaseListener.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(DatabaseListener.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(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(crypto).deriveNextSecret(secret3, 4);
+ will(returnValue(secret4.clone()));
+ 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(DatabaseListener.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();
+ }
}
diff --git a/briar-tests/src/net/sf/briar/transport/KeyRotationIntegrationTest.java b/briar-tests/src/net/sf/briar/transport/KeyRotationIntegrationTest.java
new file mode 100644
index 000000000..ed5230b83
--- /dev/null
+++ b/briar-tests/src/net/sf/briar/transport/KeyRotationIntegrationTest.java
@@ -0,0 +1,983 @@
+package net.sf.briar.transport;
+
+import static net.sf.briar.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
+import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
+import static org.junit.Assert.assertArrayEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import javax.crypto.Cipher;
+import javax.crypto.NullCipher;
+
+import net.sf.briar.BriarTestCase;
+import net.sf.briar.TestUtils;
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportId;
+import net.sf.briar.api.clock.Clock;
+import net.sf.briar.api.clock.Timer;
+import net.sf.briar.api.crypto.CryptoComponent;
+import net.sf.briar.api.crypto.ErasableKey;
+import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.event.DatabaseListener;
+import net.sf.briar.api.transport.ConnectionContext;
+import net.sf.briar.api.transport.ConnectionRecogniser;
+import net.sf.briar.api.transport.Endpoint;
+import net.sf.briar.api.transport.TemporarySecret;
+import net.sf.briar.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;
+ private final Cipher cipher;
+
+ 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;
+ cipher = new NullCipher();
+ }
+
+ @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(DatabaseListener.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(DatabaseListener.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 ErasableKey k0 = context.mock(ErasableKey.class, "k0");
+ final ErasableKey k1 = context.mock(ErasableKey.class, "k1");
+ final ErasableKey k2 = context.mock(ErasableKey.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(DatabaseListener.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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret0, false);
+ will(returnValue(k0));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret1, false);
+ will(returnValue(k1));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret0, false);
+ will(returnValue(k0));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret1, false);
+ will(returnValue(k1));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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(DatabaseListener.class)));
+ oneOf(timer).cancel();
+ }});
+
+ assertTrue(keyManager.start());
+ keyManager.endpointAdded(ep, 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 ErasableKey k0 = context.mock(ErasableKey.class, "k0");
+ final ErasableKey k1 = context.mock(ErasableKey.class, "k1");
+ final ErasableKey k2 = context.mock(ErasableKey.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(DatabaseListener.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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret0, false);
+ will(returnValue(k0));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret1, false);
+ will(returnValue(k1));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret0, false);
+ will(returnValue(k0));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret1, false);
+ will(returnValue(k1));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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(DatabaseListener.class)));
+ oneOf(timer).cancel();
+ }});
+
+ assertTrue(keyManager.start());
+ keyManager.endpointAdded(ep, 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 ErasableKey k0 = context.mock(ErasableKey.class, "k0");
+ final ErasableKey k1 = context.mock(ErasableKey.class, "k1");
+ final ErasableKey k2 = context.mock(ErasableKey.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(DatabaseListener.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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret0, false);
+ will(returnValue(k0));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret1, false);
+ will(returnValue(k1));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ with(k2), with((long) i));
+ will(new EncodeTagAction());
+ oneOf(k2).getEncoded();
+ will(returnValue(key2));
+ }
+ oneOf(k2).erase();
+ // acceptConnection()
+ oneOf(crypto).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret0, false);
+ will(returnValue(k0));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret1, false);
+ will(returnValue(k1));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 1; i < 17; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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(DatabaseListener.class)));
+ oneOf(timer).cancel();
+ }});
+
+ assertTrue(keyManager.start());
+ keyManager.endpointAdded(ep, 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 ErasableKey k0 = context.mock(ErasableKey.class, "k0");
+ final ErasableKey k1 = context.mock(ErasableKey.class, "k1");
+ final ErasableKey k2 = context.mock(ErasableKey.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(DatabaseListener.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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret0, false);
+ will(returnValue(k0));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret1, false);
+ will(returnValue(k1));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret0, false);
+ will(returnValue(k0));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret1, false);
+ will(returnValue(k1));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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(DatabaseListener.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 ErasableKey k1 = context.mock(ErasableKey.class, "k1");
+ final ErasableKey k2 = context.mock(ErasableKey.class, "k2");
+ final ErasableKey k3 = context.mock(ErasableKey.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(DatabaseListener.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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret1, false);
+ will(returnValue(k1));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret3, false);
+ will(returnValue(k3));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret1, false);
+ will(returnValue(k1));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret3, false);
+ will(returnValue(k3));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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(DatabaseListener.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 ErasableKey k2 = context.mock(ErasableKey.class, "k2");
+ final ErasableKey k3 = context.mock(ErasableKey.class, "k3");
+ final ErasableKey k4 = context.mock(ErasableKey.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(DatabaseListener.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 0
+ 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(crypto).deriveNextSecret(secret3, 4);
+ will(returnValue(secret4.clone()));
+ // 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()));
+ // One copy of each of the new secrets should be stored
+ oneOf(db).addSecrets(Arrays.asList(s3, s4));
+ // The recogniser should derive the tags for period 2
+ oneOf(crypto).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret3, false);
+ will(returnValue(k3));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret4, false);
+ will(returnValue(k4));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret2, false);
+ will(returnValue(k2));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret3, false);
+ will(returnValue(k3));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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).getTagCipher();
+ will(returnValue(cipher));
+ oneOf(crypto).deriveTagKey(secret4, false);
+ will(returnValue(k4));
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)), with(cipher),
+ 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(DatabaseListener.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);
+ ErasableKey key = (ErasableKey) invocation.getParameter(2);
+ long connection = (Long) invocation.getParameter(3);
+ byte[] rawKey = key.getEncoded();
+ encodeTag(tag, rawKey, connection);
+ return null;
+ }
+ }
+}
diff --git a/briar-tests/src/net/sf/briar/transport/TransportConnectionRecogniserTest.java b/briar-tests/src/net/sf/briar/transport/TransportConnectionRecogniserTest.java
index dddcc83d0..fe6bd2cf5 100644
--- a/briar-tests/src/net/sf/briar/transport/TransportConnectionRecogniserTest.java
+++ b/briar-tests/src/net/sf/briar/transport/TransportConnectionRecogniserTest.java
@@ -31,12 +31,12 @@ public class TransportConnectionRecogniserTest extends BriarTestCase {
private final ContactId contactId = new ContactId(234);
private final TransportId transportId =
new TransportId(TestUtils.getRandomId());
+ private final Cipher tagCipher = new NullCipher();
@Test
public void testAddAndRemoveSecret() {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
- final Cipher tagCipher = new NullCipher();
final byte[] secret = new byte[32];
new Random().nextBytes(secret);
final boolean alice = false;
@@ -48,18 +48,22 @@ public class TransportConnectionRecogniserTest extends BriarTestCase {
will(returnValue(tagCipher));
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
- exactly(16).of(crypto).encodeTag(with(any(byte[].class)),
- with(tagCipher), with(tagKey), with(any(long.class)));
- will(new EncodeTagAction());
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)),
+ with(tagCipher), with(tagKey), with((long) i));
+ will(new EncodeTagAction());
+ }
oneOf(tagKey).erase();
// Remove secret
oneOf(crypto).getTagCipher();
will(returnValue(tagCipher));
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
- exactly(16).of(crypto).encodeTag(with(any(byte[].class)),
- with(tagCipher), with(tagKey), with(any(long.class)));
- will(new EncodeTagAction());
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)),
+ with(tagCipher), with(tagKey), with((long) i));
+ will(new EncodeTagAction());
+ }
oneOf(tagKey).erase();
}});
TemporarySecret s = new TemporarySecret(contactId, transportId, 123,
@@ -77,7 +81,6 @@ public class TransportConnectionRecogniserTest extends BriarTestCase {
public void testAcceptConnection() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
- final Cipher tagCipher = new NullCipher();
final byte[] secret = new byte[32];
new Random().nextBytes(secret);
final boolean alice = false;
@@ -89,11 +92,13 @@ public class TransportConnectionRecogniserTest extends BriarTestCase {
will(returnValue(tagCipher));
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
- exactly(16).of(crypto).encodeTag(with(any(byte[].class)),
- with(tagCipher), with(tagKey), with(any(long.class)));
- will(new EncodeTagAction());
+ for(int i = 0; i < 16; i++) {
+ oneOf(crypto).encodeTag(with(any(byte[].class)),
+ with(tagCipher), with(tagKey), with((long) i));
+ will(new EncodeTagAction());
+ }
oneOf(tagKey).erase();
- // Accept connection
+ // Accept connection 0
oneOf(crypto).getTagCipher();
will(returnValue(tagCipher));
oneOf(crypto).deriveTagKey(secret, !alice);
@@ -134,6 +139,7 @@ public class TransportConnectionRecogniserTest extends BriarTestCase {
public Object invoke(Invocation invocation) throws Throwable {
byte[] tag = (byte[]) invocation.getParameter(0);
long connection = (Long) invocation.getParameter(3);
+ // Encode a fake tag based on the connection number
ByteUtils.writeUint32(connection, tag, 0);
return null;
}