From 6787d29f11d876330f76c86f28dfc37d81f1c79c Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Wed, 28 Mar 2018 11:41:50 +0100
Subject: [PATCH] Add a flag to indicate whether outgoing keys are active.

---
 .../bramble/api/crypto/TransportCrypto.java   |  3 +-
 .../bramble/api/db/DatabaseComponent.java     |  6 ++
 .../bramble/api/transport/KeyManager.java     | 10 +++
 .../bramble/api/transport/OutgoingKeys.java   | 12 +++-
 .../bramble/crypto/TransportCryptoImpl.java   |  8 ++-
 .../org/briarproject/bramble/db/Database.java |  6 ++
 .../bramble/db/DatabaseComponentImpl.java     | 10 +++
 .../briarproject/bramble/db/JdbcDatabase.java | 28 ++++++--
 .../bramble/transport/KeyManagerImpl.java     | 14 ++++
 .../transport/MutableOutgoingKeys.java        | 12 +++-
 .../transport/TransportKeyManager.java        |  2 +
 .../transport/TransportKeyManagerImpl.java    | 43 ++++++++---
 .../bramble/crypto/KeyDerivationTest.java     | 22 +++---
 .../bramble/db/DatabaseComponentImplTest.java | 19 ++---
 .../bramble/db/JdbcDatabaseTest.java          |  3 +-
 .../TransportKeyManagerImplTest.java          | 72 +++++++++++--------
 16 files changed, 201 insertions(+), 69 deletions(-)

diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/TransportCrypto.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/TransportCrypto.java
index 6385d1f015..cbd6449b48 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/TransportCrypto.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/TransportCrypto.java
@@ -14,9 +14,10 @@ public interface TransportCrypto {
 	 * rotation period from the given master secret.
 	 *
 	 * @param alice whether the keys are for use by Alice or Bob.
+	 * @param active whether the keys are usable for outgoing streams.
 	 */
 	TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
-			long rotationPeriod, boolean alice);
+			long rotationPeriod, boolean alice, boolean active);
 
 	/**
 	 * Rotates the given transport keys to the given rotation period. If the
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
index fcf5efe46c..27e379571e 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
@@ -528,6 +528,12 @@ public interface DatabaseComponent {
 	void setReorderingWindow(Transaction txn, KeySetId k, TransportId t,
 			long rotationPeriod, long base, byte[] bitmap) throws DbException;
 
+	/**
+	 * Marks the given transport keys as usable for outgoing streams.
+	 */
+	void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
+		throws DbException;
+
 	/**
 	 * Stores the given transport keys, deleting any keys they have replaced.
 	 */
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
index 06b2f47258..a3f0f6d600 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
@@ -30,6 +30,9 @@ public interface KeyManager {
 	/**
 	 * Derives and stores a set of unbound transport keys for each transport
 	 * and returns the key set IDs.
+	 * <p/>
+	 * The keys must be bound before they can be used for incoming streams,
+	 * and also activated before they can be used for outgoing streams.
 	 */
 	Map<TransportId, KeySetId> addUnboundKeys(Transaction txn, SecretKey master,
 			long timestamp, boolean alice) throws DbException;
@@ -40,6 +43,13 @@ public interface KeyManager {
 	void bindKeys(Transaction txn, ContactId c, Map<TransportId, KeySetId> keys)
 			throws DbException;
 
+	/**
+	 * Marks the given transport keys as usable for outgoing streams. Keys must
+	 * be bound before they are activated.
+	 */
+	void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
+			throws DbException;
+
 	/**
 	 * Removes the given transport keys, which must not have been bound, from
 	 * the manager and the database.
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java
index 202c46e6a0..4214ffd914 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java
@@ -10,18 +10,20 @@ public class OutgoingKeys {
 
 	private final SecretKey tagKey, headerKey;
 	private final long rotationPeriod, streamCounter;
+	private final boolean active;
 
 	public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
-			long rotationPeriod) {
-		this(tagKey, headerKey, rotationPeriod, 0);
+			long rotationPeriod, boolean active) {
+		this(tagKey, headerKey, rotationPeriod, 0, active);
 	}
 
 	public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
-			long rotationPeriod, long streamCounter) {
+			long rotationPeriod, long streamCounter, boolean active) {
 		this.tagKey = tagKey;
 		this.headerKey = headerKey;
 		this.rotationPeriod = rotationPeriod;
 		this.streamCounter = streamCounter;
+		this.active = active;
 	}
 
 	public SecretKey getTagKey() {
@@ -39,4 +41,8 @@ public class OutgoingKeys {
 	public long getStreamCounter() {
 		return streamCounter;
 	}
+
+	public boolean isActive() {
+		return active;
+	}
 }
\ No newline at end of file
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/TransportCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/TransportCryptoImpl.java
index db35c9d5e3..2d4ffb7d31 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/TransportCryptoImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/TransportCryptoImpl.java
@@ -36,7 +36,8 @@ class TransportCryptoImpl implements TransportCrypto {
 
 	@Override
 	public TransportKeys deriveTransportKeys(TransportId t,
-			SecretKey master, long rotationPeriod, boolean alice) {
+			SecretKey master, long rotationPeriod, boolean alice,
+			boolean active) {
 		// Keys for the previous period are derived from the master secret
 		SecretKey inTagPrev = deriveTagKey(master, t, !alice);
 		SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
@@ -57,7 +58,7 @@ class TransportCryptoImpl implements TransportCrypto {
 		IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
 				rotationPeriod + 1);
 		OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
-				rotationPeriod);
+				rotationPeriod, active);
 		// Collect and return the keys
 		return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
 	}
@@ -71,6 +72,7 @@ class TransportCryptoImpl implements TransportCrypto {
 		IncomingKeys inNext = k.getNextIncomingKeys();
 		OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
 		long startPeriod = outCurr.getRotationPeriod();
+		boolean active = outCurr.isActive();
 		// Rotate the keys
 		for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
 			inPrev = inCurr;
@@ -80,7 +82,7 @@ class TransportCryptoImpl implements TransportCrypto {
 			inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
 			SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
 			SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
-			outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
+			outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p, active);
 		}
 		// Collect and return the keys
 		return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
index 6ee996b2b8..ddd4860c2c 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
@@ -640,6 +640,12 @@ interface Database<T> {
 	void setReorderingWindow(T txn, KeySetId k, TransportId t,
 			long rotationPeriod, long base, byte[] bitmap) throws DbException;
 
+	/**
+	 * Marks the given transport keys as usable for outgoing streams.
+	 */
+	void setTransportKeysActive(T txn, TransportId t, KeySetId k)
+		throws DbException;
+
 	/**
 	 * Updates the transmission count and expiry time of the given message
 	 * with respect to the given contact, using the latency of the transport
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
index 697bcdd3d6..a974802005 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
@@ -890,6 +890,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		db.setReorderingWindow(txn, k, t, rotationPeriod, base, bitmap);
 	}
 
+	@Override
+	public void setTransportKeysActive(Transaction transaction, TransportId t,
+			KeySetId k) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
+		T txn = unbox(transaction);
+		if (!db.containsTransport(txn, t))
+			throw new NoSuchTransportException();
+		db.setTransportKeysActive(txn, t, k);
+	}
+
 	@Override
 	public void updateTransportKeys(Transaction transaction,
 			Collection<KeySet> keys) throws DbException {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
index 25cfb4d5aa..953a0f1604 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
@@ -234,6 +234,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " tagKey _SECRET NOT NULL,"
 					+ " headerKey _SECRET NOT NULL,"
 					+ " stream BIGINT NOT NULL,"
+					+ " active BOOLEAN NOT NULL,"
 					+ " PRIMARY KEY (transportId, keySetId),"
 					+ " FOREIGN KEY (transportId)"
 					+ " REFERENCES transports (transportId)"
@@ -880,8 +881,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		try {
 			// Store the outgoing keys
 			String sql = "INSERT INTO outgoingKeys (contactId, transportId,"
-					+ " rotationPeriod, tagKey, headerKey, stream)"
-					+ " VALUES (?, ?, ?, ?, ?, ?)";
+					+ " rotationPeriod, tagKey, headerKey, stream, active)"
+					+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			if (c == null) ps.setNull(1, INTEGER);
 			else ps.setInt(1, c.getInt());
@@ -891,6 +892,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps.setBytes(4, outCurr.getTagKey().getBytes());
 			ps.setBytes(5, outCurr.getHeaderKey().getBytes());
 			ps.setLong(6, outCurr.getStreamCounter());
+			ps.setBoolean(7, outCurr.isActive());
 			int affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
@@ -2157,7 +2159,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps.close();
 			// Retrieve the outgoing keys in the same order
 			sql = "SELECT keySetId, contactId, rotationPeriod,"
-					+ " tagKey, headerKey, stream"
+					+ " tagKey, headerKey, stream, active"
 					+ " FROM outgoingKeys"
 					+ " WHERE transportId = ?"
 					+ " ORDER BY keySetId";
@@ -2175,8 +2177,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 				SecretKey tagKey = new SecretKey(rs.getBytes(4));
 				SecretKey headerKey = new SecretKey(rs.getBytes(5));
 				long streamCounter = rs.getLong(6);
+				boolean active = rs.getBoolean(7);
 				OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
-						rotationPeriod, streamCounter);
+						rotationPeriod, streamCounter, active);
 				IncomingKeys inPrev = inKeys.get(i * 3);
 				IncomingKeys inCurr = inKeys.get(i * 3 + 1);
 				IncomingKeys inNext = inKeys.get(i * 3 + 2);
@@ -2889,6 +2892,23 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	@Override
+	public void setTransportKeysActive(Connection txn, TransportId t,
+			KeySetId k) throws DbException {
+		PreparedStatement ps = null;
+		try {
+			String sql = "UPDATE outgoingKeys SET active = true"
+					+ " WHERE transportId = ? AND keySetId = ?";
+			ps = txn.prepareStatement(sql);
+			int affected = ps.executeUpdate();
+			if (affected < 0 || affected > 1) throw new DbStateException();
+			ps.close();
+		} catch (SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	@Override
 	public void updateExpiryTime(Connection txn, ContactId c, MessageId m,
 			int maxLatency) throws DbException {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
index 1a06ef4a65..860e0e4029 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
@@ -132,6 +132,20 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 		}
 	}
 
+	@Override
+	public void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
+			throws DbException {
+		for (Entry<TransportId, KeySetId> e : keys.entrySet()) {
+			TransportId t = e.getKey();
+			TransportKeyManager m = managers.get(t);
+			if (m == null) {
+				if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
+			} else {
+				m.activateKeys(txn, e.getValue());
+			}
+		}
+	}
+
 	@Override
 	public void removeKeys(Transaction txn, Map<TransportId, KeySetId> keys)
 			throws DbException {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableOutgoingKeys.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableOutgoingKeys.java
index aaafec13bd..c195f445cd 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableOutgoingKeys.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableOutgoingKeys.java
@@ -13,17 +13,19 @@ class MutableOutgoingKeys {
 	private final SecretKey tagKey, headerKey;
 	private final long rotationPeriod;
 	private long streamCounter;
+	private boolean active;
 
 	MutableOutgoingKeys(OutgoingKeys out) {
 		tagKey = out.getTagKey();
 		headerKey = out.getHeaderKey();
 		rotationPeriod = out.getRotationPeriod();
 		streamCounter = out.getStreamCounter();
+		active = out.isActive();
 	}
 
 	OutgoingKeys snapshot() {
 		return new OutgoingKeys(tagKey, headerKey, rotationPeriod,
-				streamCounter);
+				streamCounter, active);
 	}
 
 	SecretKey getTagKey() {
@@ -45,4 +47,12 @@ class MutableOutgoingKeys {
 	void incrementStreamCounter() {
 		streamCounter++;
 	}
+
+	boolean isActive() {
+		return active;
+	}
+
+	void activate() {
+		active = true;
+	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java
index 4eff0b289d..2af01fa4a6 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java
@@ -23,6 +23,8 @@ interface TransportKeyManager {
 
 	void bindKeys(Transaction txn, ContactId c, KeySetId k) throws DbException;
 
+	void activateKeys(Transaction txn, KeySetId k) throws DbException;
+
 	void removeKeys(Transaction txn, KeySetId k) throws DbException;
 
 	void removeContact(ContactId c);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
index 889485d0dc..d7ccb49c26 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
@@ -127,10 +127,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			encodeTags(keySetId, contactId, m.getPreviousIncomingKeys());
 			encodeTags(keySetId, contactId, m.getCurrentIncomingKeys());
 			encodeTags(keySetId, contactId, m.getNextIncomingKeys());
-			// Use the outgoing keys with the highest key set ID
-			MutableKeySet old = outContexts.get(contactId);
-			if (old == null || old.getKeySetId().getInt() < keySetId.getInt())
-				outContexts.put(contactId, ks);
+			considerReplacingOutgoingKeys(ks);
 		}
 	}
 
@@ -147,6 +144,17 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 		}
 	}
 
+	// Locking: lock
+	private void considerReplacingOutgoingKeys(MutableKeySet ks) {
+		// Use the active outgoing keys with the highest key set ID
+		if (ks.getTransportKeys().getCurrentOutgoingKeys().isActive()) {
+			MutableKeySet old = outContexts.get(ks.getContactId());
+			if (old == null ||
+					old.getKeySetId().getInt() < ks.getKeySetId().getInt())
+				outContexts.put(ks.getContactId(), ks);
+		}
+	}
+
 	private void scheduleKeyRotation(long now) {
 		long delay = rotationPeriodLength - now % rotationPeriodLength;
 		scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS);
@@ -171,17 +179,17 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 	@Override
 	public void addContact(Transaction txn, ContactId c, SecretKey master,
 			long timestamp, boolean alice) throws DbException {
-		deriveAndAddKeys(txn, c, master, timestamp, alice);
+		deriveAndAddKeys(txn, c, master, timestamp, alice, true);
 	}
 
 	@Override
 	public KeySetId addUnboundKeys(Transaction txn, SecretKey master,
 			long timestamp, boolean alice) throws DbException {
-		return deriveAndAddKeys(txn, null, master, timestamp, alice);
+		return deriveAndAddKeys(txn, null, master, timestamp, alice, false);
 	}
 
 	private KeySetId deriveAndAddKeys(Transaction txn, @Nullable ContactId c,
-			SecretKey master, long timestamp, boolean alice)
+			SecretKey master, long timestamp, boolean alice, boolean active)
 			throws DbException {
 		lock.lock();
 		try {
@@ -189,7 +197,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			long rotationPeriod = timestamp / rotationPeriodLength;
 			// Derive the transport keys
 			TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
-					master, rotationPeriod, alice);
+					master, rotationPeriod, alice, active);
 			// Rotate the keys to the current rotation period if necessary
 			rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
 			k = transportCrypto.rotateTransportKeys(k, rotationPeriod);
@@ -210,6 +218,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 		try {
 			MutableKeySet ks = keys.get(k);
 			if (ks == null) throw new IllegalArgumentException();
+			// Check that the keys haven't already been bound
 			if (ks.getContactId() != null) throw new IllegalArgumentException();
 			MutableTransportKeys m = ks.getTransportKeys();
 			addKeys(k, c, m);
@@ -219,12 +228,30 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 		}
 	}
 
+	@Override
+	public void activateKeys(Transaction txn, KeySetId k) throws DbException {
+		lock.lock();
+		try {
+			MutableKeySet ks = keys.get(k);
+			if (ks == null) throw new IllegalArgumentException();
+			// Check that the keys have been bound
+			if (ks.getContactId() == null) throw new IllegalArgumentException();
+			MutableTransportKeys m = ks.getTransportKeys();
+			m.getCurrentOutgoingKeys().activate();
+			considerReplacingOutgoingKeys(ks);
+			db.setTransportKeysActive(txn, m.getTransportId(), k);
+		} finally {
+			lock.unlock();
+		}
+	}
+
 	@Override
 	public void removeKeys(Transaction txn, KeySetId k) throws DbException {
 		lock.lock();
 		try {
 			MutableKeySet ks = keys.remove(k);
 			if (ks == null) throw new IllegalArgumentException();
+			// Check that the keys haven't been bound
 			if (ks.getContactId() != null) throw new IllegalArgumentException();
 			TransportId t = ks.getTransportKeys().getTransportId();
 			db.removeTransportKeys(txn, t, k);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java
index 81f73f8d28..dc51966ffb 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java
@@ -33,7 +33,7 @@ public class KeyDerivationTest extends BrambleTestCase {
 	@Test
 	public void testKeysAreDistinct() {
 		TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
-				master, 123, true);
+				master, 123, true, true);
 		assertAllDifferent(k);
 	}
 
@@ -41,9 +41,9 @@ public class KeyDerivationTest extends BrambleTestCase {
 	public void testCurrentKeysMatchCurrentKeysOfContact() {
 		// Start in rotation period 123
 		TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
-				master, 123, true);
+				master, 123, true, true);
 		TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
-				master, 123, false);
+				master, 123, false, true);
 		// Alice's incoming keys should equal Bob's outgoing keys
 		assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
 				kB.getCurrentOutgoingKeys().getTagKey().getBytes());
@@ -73,9 +73,9 @@ public class KeyDerivationTest extends BrambleTestCase {
 	public void testPreviousKeysMatchPreviousKeysOfContact() {
 		// Start in rotation period 123
 		TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
-				master, 123, true);
+				master, 123, true, true);
 		TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
-				master, 123, false);
+				master, 123, false, true);
 		// Compare Alice's previous keys in period 456 with Bob's current keys
 		// in period 455
 		kA = transportCrypto.rotateTransportKeys(kA, 456);
@@ -100,9 +100,9 @@ public class KeyDerivationTest extends BrambleTestCase {
 	public void testNextKeysMatchNextKeysOfContact() {
 		// Start in rotation period 123
 		TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
-				master, 123, true);
+				master, 123, true, true);
 		TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
-				master, 123, false);
+				master, 123, false, true);
 		// Compare Alice's current keys in period 456 with Bob's next keys in
 		// period 455
 		kA = transportCrypto.rotateTransportKeys(kA, 456);
@@ -127,9 +127,9 @@ public class KeyDerivationTest extends BrambleTestCase {
 		SecretKey master1 = getSecretKey();
 		assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
 		TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
-				master, 123, true);
+				master, 123, true, true);
 		TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
-				master1, 123, true);
+				master1, 123, true, true);
 		assertAllDifferent(k, k1);
 	}
 
@@ -138,9 +138,9 @@ public class KeyDerivationTest extends BrambleTestCase {
 		TransportId transportId1 = new TransportId("id1");
 		assertFalse(transportId.getString().equals(transportId1.getString()));
 		TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
-				master, 123, true);
+				master, 123, true, true);
 		TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
-				master, 123, true);
+				master, 123, true, true);
 		assertAllDifferent(k, k1);
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
index 61a4991628..d8f1f59fbf 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
@@ -71,6 +71,7 @@ import static org.briarproject.bramble.api.transport.TransportConstants.REORDERI
 import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
 import static org.briarproject.bramble.test.TestUtils.getAuthor;
 import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
+import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -1356,22 +1357,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 	}
 
 	private TransportKeys createTransportKeys() {
-		SecretKey inPrevTagKey = TestUtils.getSecretKey();
-		SecretKey inPrevHeaderKey = TestUtils.getSecretKey();
+		SecretKey inPrevTagKey = getSecretKey();
+		SecretKey inPrevHeaderKey = getSecretKey();
 		IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
 				1, 123, new byte[4]);
-		SecretKey inCurrTagKey = TestUtils.getSecretKey();
-		SecretKey inCurrHeaderKey = TestUtils.getSecretKey();
+		SecretKey inCurrTagKey = getSecretKey();
+		SecretKey inCurrHeaderKey = getSecretKey();
 		IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
 				2, 234, new byte[4]);
-		SecretKey inNextTagKey = TestUtils.getSecretKey();
-		SecretKey inNextHeaderKey = TestUtils.getSecretKey();
+		SecretKey inNextTagKey = getSecretKey();
+		SecretKey inNextHeaderKey = getSecretKey();
 		IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
 				3, 345, new byte[4]);
-		SecretKey outCurrTagKey = TestUtils.getSecretKey();
-		SecretKey outCurrHeaderKey = TestUtils.getSecretKey();
+		SecretKey outCurrTagKey = getSecretKey();
+		SecretKey outCurrHeaderKey = getSecretKey();
 		OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
-				2, 456);
+				2, 456, true);
 		return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
index 6babc64594..9e4047982d 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
@@ -804,6 +804,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 				actual.getHeaderKey().getBytes());
 		assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
 		assertEquals(expected.getStreamCounter(), actual.getStreamCounter());
+		assertEquals(expected.isActive(), actual.isActive());
 	}
 
 	@Test
@@ -1820,7 +1821,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 		SecretKey outCurrTagKey = getSecretKey();
 		SecretKey outCurrHeaderKey = getSecretKey();
 		OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
-				2, 456);
+				2, 456, true);
 		return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
index afc5dfed2d..8da8eefaa4 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
@@ -65,16 +65,16 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 
 	@Test
 	public void testKeysAreRotatedAtStartup() throws Exception {
-		TransportKeys shouldRotate = createTransportKeys(900, 0);
-		TransportKeys shouldNotRotate = createTransportKeys(1000, 0);
-		TransportKeys shouldRotate1 = createTransportKeys(999, 0);
+		TransportKeys shouldRotate = createTransportKeys(900, 0, true);
+		TransportKeys shouldNotRotate = createTransportKeys(1000, 0, true);
+		TransportKeys shouldRotate1 = createTransportKeys(999, 0, false);
 		Collection<KeySet> loaded = asList(
 				new KeySet(keySetId, contactId, shouldRotate),
 				new KeySet(keySetId1, contactId1, shouldNotRotate),
 				new KeySet(keySetId2, null, shouldRotate1)
 		);
-		TransportKeys rotated = createTransportKeys(1000, 0);
-		TransportKeys rotated1 = createTransportKeys(1000, 0);
+		TransportKeys rotated = createTransportKeys(1000, 0, true);
+		TransportKeys rotated1 = createTransportKeys(1000, 0, false);
 		Transaction txn = new Transaction(null, false);
 
 		context.checking(new Expectations() {{
@@ -117,13 +117,13 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 	@Test
 	public void testKeysAreRotatedWhenAddingContact() throws Exception {
 		boolean alice = random.nextBoolean();
-		TransportKeys transportKeys = createTransportKeys(999, 0);
-		TransportKeys rotated = createTransportKeys(1000, 0);
+		TransportKeys transportKeys = createTransportKeys(999, 0, true);
+		TransportKeys rotated = createTransportKeys(1000, 0, true);
 		Transaction txn = new Transaction(null, false);
 
 		context.checking(new Expectations() {{
 			oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
-					999, alice);
+					999, alice, true);
 			will(returnValue(transportKeys));
 			// Get the current time (1 ms after start of rotation period 1000)
 			oneOf(clock).currentTimeMillis();
@@ -155,13 +155,13 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 	@Test
 	public void testKeysAreRotatedWhenAddingUnboundKeys() throws Exception {
 		boolean alice = random.nextBoolean();
-		TransportKeys transportKeys = createTransportKeys(999, 0);
-		TransportKeys rotated = createTransportKeys(1000, 0);
+		TransportKeys transportKeys = createTransportKeys(999, 0, false);
+		TransportKeys rotated = createTransportKeys(1000, 0, false);
 		Transaction txn = new Transaction(null, false);
 
 		context.checking(new Expectations() {{
 			oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
-					999, alice);
+					999, alice, false);
 			will(returnValue(transportKeys));
 			// Get the current time (1 ms after start of rotation period 1000)
 			oneOf(clock).currentTimeMillis();
@@ -200,7 +200,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		boolean alice = random.nextBoolean();
 		// The stream counter has been exhausted
 		TransportKeys transportKeys = createTransportKeys(1000,
-				MAX_32_BIT_UNSIGNED + 1);
+				MAX_32_BIT_UNSIGNED + 1, true);
 		Transaction txn = new Transaction(null, false);
 
 		expectAddContactNoRotation(alice, transportKeys, txn);
@@ -220,7 +220,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		boolean alice = random.nextBoolean();
 		// The stream counter can be used one more time before being exhausted
 		TransportKeys transportKeys = createTransportKeys(1000,
-				MAX_32_BIT_UNSIGNED);
+				MAX_32_BIT_UNSIGNED, true);
 		Transaction txn = new Transaction(null, false);
 
 		expectAddContactNoRotation(alice, transportKeys, txn);
@@ -254,7 +254,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 	public void testIncomingStreamContextIsNullIfTagIsNotFound()
 			throws Exception {
 		boolean alice = random.nextBoolean();
-		TransportKeys transportKeys = createTransportKeys(1000, 0);
+		TransportKeys transportKeys = createTransportKeys(1000, 0, true);
 		Transaction txn = new Transaction(null, false);
 
 		expectAddContactNoRotation(alice, transportKeys, txn);
@@ -274,14 +274,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 	@Test
 	public void testTagIsNotRecognisedTwice() throws Exception {
 		boolean alice = random.nextBoolean();
-		TransportKeys transportKeys = createTransportKeys(1000, 0);
+		TransportKeys transportKeys = createTransportKeys(1000, 0, true);
 		// Keep a copy of the tags
 		List<byte[]> tags = new ArrayList<>();
 		Transaction txn = new Transaction(null, false);
 
 		context.checking(new Expectations() {{
 			oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
-					1000, alice);
+					1000, alice, true);
 			will(returnValue(transportKeys));
 			// Get the current time (the start of rotation period 1000)
 			oneOf(clock).currentTimeMillis();
@@ -335,14 +335,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 
 	@Test
 	public void testKeysAreRotatedToCurrentPeriod() throws Exception {
-		TransportKeys transportKeys = createTransportKeys(1000, 0);
-		TransportKeys transportKeys1 = createTransportKeys(1000, 0);
+		TransportKeys transportKeys = createTransportKeys(1000, 0, true);
+		TransportKeys transportKeys1 = createTransportKeys(1000, 0, false);
 		Collection<KeySet> loaded = asList(
 				new KeySet(keySetId, contactId, transportKeys),
 				new KeySet(keySetId1, null, transportKeys1)
 		);
-		TransportKeys rotated = createTransportKeys(1001, 0);
-		TransportKeys rotated1 = createTransportKeys(1001, 0);
+		TransportKeys rotated = createTransportKeys(1001, 0, true);
+		TransportKeys rotated1 = createTransportKeys(1001, 0, false);
 		Transaction txn = new Transaction(null, false);
 		Transaction txn1 = new Transaction(null, false);
 
@@ -411,14 +411,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 	}
 
 	@Test
-	public void testTagsAreEncodedWhenKeysAreBound() throws Exception {
+	public void testBindingAndActivatingKeys() throws Exception {
 		boolean alice = random.nextBoolean();
-		TransportKeys transportKeys = createTransportKeys(1000, 0);
+		TransportKeys transportKeys = createTransportKeys(1000, 0, false);
 		Transaction txn = new Transaction(null, false);
 
 		context.checking(new Expectations() {{
 			oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
-					1000, alice);
+					1000, alice, false);
 			will(returnValue(transportKeys));
 			// Get the current time (the start of rotation period 1000)
 			oneOf(clock).currentTimeMillis();
@@ -438,6 +438,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 			}
 			// Save the key binding
 			oneOf(db).bindTransportKeys(txn, contactId, transportId, keySetId);
+			// Activate the keys
+			oneOf(db).setTransportKeysActive(txn, transportId, keySetId);
+			// Increment the stream counter
+			oneOf(db).incrementStreamCounter(txn, contactId, transportId, 1000);
 		}});
 
 		TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
@@ -448,17 +452,29 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
 				masterKey, timestamp, alice));
 		transportKeyManager.bindKeys(txn, contactId, keySetId);
+		// The keys are inactive so no stream context should be returned
+		assertNull(transportKeyManager.getStreamContext(txn, contactId));
+		transportKeyManager.activateKeys(txn, keySetId);
+		// The keys are active so a stream context should be returned
+		StreamContext ctx = transportKeyManager.getStreamContext(txn,
+				contactId);
+		assertNotNull(ctx);
+		assertEquals(contactId, ctx.getContactId());
+		assertEquals(transportId, ctx.getTransportId());
+		assertEquals(tagKey, ctx.getTagKey());
+		assertEquals(headerKey, ctx.getHeaderKey());
+		assertEquals(0, ctx.getStreamNumber());
 	}
 
 	@Test
 	public void testRemovingUnboundKeys() throws Exception {
 		boolean alice = random.nextBoolean();
-		TransportKeys transportKeys = createTransportKeys(1000, 0);
+		TransportKeys transportKeys = createTransportKeys(1000, 0, false);
 		Transaction txn = new Transaction(null, false);
 
 		context.checking(new Expectations() {{
 			oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
-					1000, alice);
+					1000, alice, false);
 			will(returnValue(transportKeys));
 			// Get the current time (the start of rotation period 1000)
 			oneOf(clock).currentTimeMillis();
@@ -487,7 +503,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 			TransportKeys transportKeys, Transaction txn) throws Exception {
 		context.checking(new Expectations() {{
 			oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
-					1000, alice);
+					1000, alice, true);
 			will(returnValue(transportKeys));
 			// Get the current time (the start of rotation period 1000)
 			oneOf(clock).currentTimeMillis();
@@ -509,7 +525,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 	}
 
 	private TransportKeys createTransportKeys(long rotationPeriod,
-			long streamCounter) {
+			long streamCounter, boolean active) {
 		IncomingKeys inPrev = new IncomingKeys(tagKey, headerKey,
 				rotationPeriod - 1);
 		IncomingKeys inCurr = new IncomingKeys(tagKey, headerKey,
@@ -517,7 +533,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		IncomingKeys inNext = new IncomingKeys(tagKey, headerKey,
 				rotationPeriod + 1);
 		OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
-				rotationPeriod, streamCounter);
+				rotationPeriod, streamCounter, active);
 		return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
 	}
 
-- 
GitLab