diff --git a/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java b/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java index b1b1355a0dd68944036f87368c48127f80514b5b..6ab4d1730247cdc4040f9d963bfaedfa85009805 100644 --- a/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java +++ b/briar-core/src/net/sf/briar/transport/KeyManagerImpl.java @@ -1,5 +1,6 @@ package net.sf.briar.transport; +import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static net.sf.briar.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE; @@ -114,6 +115,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener { // Discard the secret if the transport has been removed Long maxLatency = maxLatencies.get(s.getTransportId()); if(maxLatency == null) { + if(LOG.isLoggable(INFO)) LOG.info("Discarding obsolete secret"); ByteUtils.erase(s.getSecret()); continue; } @@ -165,8 +167,8 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener { TemporarySecret s1 = new TemporarySecret(s, currentPeriod - 1, b1); TemporarySecret s2 = new TemporarySecret(s, currentPeriod, b2); TemporarySecret s3 = new TemporarySecret(s, currentPeriod + 1, b3); - // Add the secrets to their respective maps - the old and current - // secrets may already exist, in which case erase the duplicates + // Add the secrets to their respective maps - copies may already + // exist, in which case erase the duplicates EndpointKey k = new EndpointKey(s); TemporarySecret exists = oldSecrets.put(k, s1); if(exists == null) created.add(s1); @@ -174,8 +176,9 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener { exists = currentSecrets.put(k, s2); if(exists == null) created.add(s2); else ByteUtils.erase(exists.getSecret()); - newSecrets.put(k, s3); - created.add(s3); + exists = newSecrets.put(k, s3); + if(exists == null) created.add(s3); + else ByteUtils.erase(exists.getSecret()); } return created; } @@ -199,10 +202,17 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener { public synchronized ConnectionContext getConnectionContext(ContactId c, TransportId t) { TemporarySecret s = currentSecrets.get(new EndpointKey(c, t)); - if(s == null) return null; + if(s == null) { + if(LOG.isLoggable(INFO)) LOG.info("No secret for endpoint"); + return null; + } long connection; try { connection = db.incrementConnectionCounter(c, t, s.getPeriod()); + if(connection == -1) { + if(LOG.isLoggable(INFO)) LOG.info("No counter for period"); + return null; + } } catch(DbException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); return null; @@ -214,7 +224,8 @@ class KeyManagerImpl extends TimerTask implements KeyManager, DatabaseListener { public synchronized void endpointAdded(Endpoint ep, byte[] initialSecret) { Long maxLatency = maxLatencies.get(ep.getTransportId()); if(maxLatency == null) { - if(LOG.isLoggable(WARNING)) LOG.warning("No such transport"); + if(LOG.isLoggable(INFO)) + LOG.info("No such transport, ignoring endpoint"); return; } // Work out which rotation period we're in diff --git a/briar-tests/src/net/sf/briar/transport/KeyManagerImplTest.java b/briar-tests/src/net/sf/briar/transport/KeyManagerImplTest.java index 631134b6eaffe263ac5b50a545814bdbbda0e120..1e6bf41c9e36537b01b644cb77555f9c4f8da868 100644 --- a/briar-tests/src/net/sf/briar/transport/KeyManagerImplTest.java +++ b/briar-tests/src/net/sf/briar/transport/KeyManagerImplTest.java @@ -1,11 +1,9 @@ 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.Random; import java.util.TimerTask; import net.sf.briar.BriarTestCase; @@ -23,36 +21,32 @@ import net.sf.briar.api.transport.TemporarySecret; import org.jmock.Expectations; import org.jmock.Mockery; -import org.junit.Before; import org.junit.Test; public class KeyManagerImplTest extends BriarTestCase { - private final Random random = new Random(); + 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 long maxLatency; - private final long rotationPeriodLength; - private final byte[] secret0, secret1, secret2, secret3; - private final long epoch = 1000L * 1000L * 1000L * 1000L; + private final byte[] secret0, secret1, secret2, secret3, secret4; public KeyManagerImplTest() { contactId = new ContactId(234); transportId = new TransportId(TestUtils.getRandomId()); - maxLatency = 2 * 60 * 1000; // 2 minutes - rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; secret0 = new byte[32]; secret1 = new byte[32]; secret2 = new byte[32]; secret3 = new byte[32]; - } - - @Before - public void setUp() { - random.nextBytes(secret0); - random.nextBytes(secret1); - random.nextBytes(secret2); - random.nextBytes(secret3); + 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; } @Test @@ -64,8 +58,10 @@ public class KeyManagerImplTest extends BriarTestCase { context.mock(ConnectionRecogniser.class); final Clock clock = context.mock(Clock.class); final Timer timer = context.mock(Timer.class); + final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db, connectionRecogniser, clock, timer); + context.checking(new Expectations() {{ // start() oneOf(db).addListener(with(any(DatabaseListener.class))); @@ -74,7 +70,7 @@ public class KeyManagerImplTest extends BriarTestCase { oneOf(db).getTransportLatencies(); will(returnValue(Collections.emptyMap())); oneOf(clock).currentTimeMillis(); - will(returnValue(epoch)); + will(returnValue(EPOCH)); oneOf(timer).scheduleAtFixedRate(with(any(TimerTask.class)), with(any(long.class)), with(any(long.class))); // stop() @@ -98,13 +94,16 @@ public class KeyManagerImplTest extends BriarTestCase { 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 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); + 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))); @@ -112,10 +111,10 @@ public class KeyManagerImplTest extends BriarTestCase { will(returnValue(Arrays.asList(s0, s1, s2))); oneOf(db).getTransportLatencies(); will(returnValue(Collections.singletonMap(transportId, - maxLatency))); - // The current time is the second secret's activation time + MAX_LATENCY))); + // The current time is the epoch, the start of period 1 oneOf(clock).currentTimeMillis(); - will(returnValue(epoch)); + will(returnValue(EPOCH)); // The secrets for periods 0 - 2 should be added to the recogniser oneOf(connectionRecogniser).addSecret(s0); oneOf(connectionRecogniser).addSecret(s1); @@ -135,7 +134,7 @@ public class KeyManagerImplTest extends BriarTestCase { } @Test - public void testLoadSecretsAtNewActivationTime() throws Exception { + public void testLoadSecretsAtStartOfPeriod2() throws Exception { Mockery context = new Mockery(); final CryptoComponent crypto = context.mock(CryptoComponent.class); final DatabaseComponent db = context.mock(DatabaseComponent.class); @@ -143,15 +142,18 @@ public class KeyManagerImplTest extends BriarTestCase { 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 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); - // A fourth secret should be derived and stored - final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3); + 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))); @@ -159,17 +161,17 @@ public class KeyManagerImplTest extends BriarTestCase { will(returnValue(Arrays.asList(s0, s1, s2))); oneOf(db).getTransportLatencies(); will(returnValue(Collections.singletonMap(transportId, - maxLatency))); - // The current time is the third secret's activation time + MAX_LATENCY))); + // The current time is the start of period 2 oneOf(clock).currentTimeMillis(); - will(returnValue(epoch + rotationPeriodLength)); - // A fourth secret should be derived and stored + 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)); + will(returnValue(secret3.clone())); oneOf(db).addSecrets(Arrays.asList(s3)); // The secrets for periods 1 - 3 should be added to the recogniser oneOf(connectionRecogniser).addSecret(s1); @@ -184,8 +186,75 @@ public class KeyManagerImplTest extends BriarTestCase { }}); assertTrue(keyManager.start()); - // The dead secret should have been erased - assertArrayEquals(new byte[32], secret0); + keyManager.stop(); + + context.assertIsSatisfied(); + } + + @Test + public void testLoadSecretsAtStartOfPeriod3() 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 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 start of period 3 + oneOf(clock).currentTimeMillis(); + will(returnValue(EPOCH + 2 * ROTATION_PERIOD_LENGTH)); + // 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 secrets for periods 2 - 3 should be added to the recogniser + oneOf(connectionRecogniser).addSecret(s2); + oneOf(connectionRecogniser).addSecret(s3); + oneOf(connectionRecogniser).addSecret(s4); + oneOf(timer).scheduleAtFixedRate(with(any(TimerTask.class)), + with(any(long.class)), with(any(long.class))); + // stop() + oneOf(db).removeListener(with(any(DatabaseListener.class))); + oneOf(timer).cancel(); + oneOf(connectionRecogniser).removeSecrets(); + }}); + + assertTrue(keyManager.start()); keyManager.stop(); context.assertIsSatisfied();