diff --git a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java index 7d75fd3b3498b894e50fb0899e0a63f3045f18c5..a6c8f1be1271a857591c46e27185c477d50a7a7c 100644 --- a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java +++ b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java @@ -3,7 +3,6 @@ package org.briarproject.transport; import org.briarproject.api.TransportId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; -import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseExecutor; @@ -18,7 +17,6 @@ import org.briarproject.api.lifecycle.ServiceException; import org.briarproject.api.plugins.PluginConfig; import org.briarproject.api.plugins.duplex.DuplexPluginFactory; import org.briarproject.api.plugins.simplex.SimplexPluginFactory; -import org.briarproject.api.system.Clock; import org.briarproject.api.transport.KeyManager; import org.briarproject.api.transport.StreamContext; @@ -27,7 +25,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; @@ -41,26 +38,21 @@ class KeyManagerImpl implements KeyManager, Service, EventListener { Logger.getLogger(KeyManagerImpl.class.getName()); private final DatabaseComponent db; - private final CryptoComponent crypto; private final Executor dbExecutor; - private final ScheduledExecutorService scheduler; private final PluginConfig pluginConfig; - private final Clock clock; + private final TransportKeyManagerFactory transportKeyManagerFactory; private final Map<ContactId, Boolean> activeContacts; private final ConcurrentHashMap<TransportId, TransportKeyManager> managers; private final AtomicBoolean used = new AtomicBoolean(false); @Inject - KeyManagerImpl(DatabaseComponent db, CryptoComponent crypto, - @DatabaseExecutor Executor dbExecutor, - ScheduledExecutorService scheduler, PluginConfig pluginConfig, - Clock clock) { + KeyManagerImpl(DatabaseComponent db, @DatabaseExecutor Executor dbExecutor, + PluginConfig pluginConfig, + TransportKeyManagerFactory transportKeyManagerFactory) { this.db = db; - this.crypto = crypto; this.dbExecutor = dbExecutor; - this.scheduler = scheduler; this.pluginConfig = pluginConfig; - this.clock = clock; + this.transportKeyManagerFactory = transportKeyManagerFactory; // Use a ConcurrentHashMap as a thread-safe set activeContacts = new ConcurrentHashMap<ContactId, Boolean>(); managers = new ConcurrentHashMap<TransportId, TransportKeyManager>(); @@ -83,9 +75,9 @@ class KeyManagerImpl implements KeyManager, Service, EventListener { for (Entry<TransportId, Integer> e : transports.entrySet()) db.addTransport(txn, e.getKey(), e.getValue()); for (Entry<TransportId, Integer> e : transports.entrySet()) { - TransportKeyManager m = new TransportKeyManager(db, crypto, - dbExecutor, scheduler, clock, e.getKey(), - e.getValue()); + TransportKeyManager m = transportKeyManagerFactory + .createTransportKeyManager(e.getKey(), + e.getValue()); managers.put(e.getKey(), m); m.start(txn); } diff --git a/briar-core/src/org/briarproject/transport/TransportKeyManager.java b/briar-core/src/org/briarproject/transport/TransportKeyManager.java index 4210c31ed4a961b0694587435609e85635450ee3..56ed6b9ca5909a98c9224a0d52e1b81e731bbea0 100644 --- a/briar-core/src/org/briarproject/transport/TransportKeyManager.java +++ b/briar-core/src/org/briarproject/transport/TransportKeyManager.java @@ -1,303 +1,24 @@ package org.briarproject.transport; -import org.briarproject.api.Bytes; -import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; -import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.SecretKey; -import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; -import org.briarproject.api.system.Clock; import org.briarproject.api.transport.StreamContext; -import org.briarproject.api.transport.TransportKeys; -import org.briarproject.transport.ReorderingWindow.Change; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.locks.ReentrantLock; -import java.util.logging.Logger; +interface TransportKeyManager { -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.logging.Level.WARNING; -import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE; -import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH; -import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED; - -class TransportKeyManager { - - private static final Logger LOG = - Logger.getLogger(TransportKeyManager.class.getName()); - - private final DatabaseComponent db; - private final CryptoComponent crypto; - private final Executor dbExecutor; - private final ScheduledExecutorService scheduler; - private final Clock clock; - private final TransportId transportId; - private final long rotationPeriodLength; - private final ReentrantLock lock; - - // The following are locking: lock - private final Map<Bytes, TagContext> inContexts; - private final Map<ContactId, MutableOutgoingKeys> outContexts; - private final Map<ContactId, MutableTransportKeys> keys; - - TransportKeyManager(DatabaseComponent db, CryptoComponent crypto, - Executor dbExecutor, ScheduledExecutorService scheduler, - Clock clock, TransportId transportId, long maxLatency) { - this.db = db; - this.crypto = crypto; - this.dbExecutor = dbExecutor; - this.scheduler = scheduler; - this.clock = clock; - this.transportId = transportId; - rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; - lock = new ReentrantLock(); - inContexts = new HashMap<Bytes, TagContext>(); - outContexts = new HashMap<ContactId, MutableOutgoingKeys>(); - keys = new HashMap<ContactId, MutableTransportKeys>(); - } - - void start(Transaction txn) throws DbException { - long now = clock.currentTimeMillis(); - lock.lock(); - try { - // Load the transport keys from the DB - Map<ContactId, TransportKeys> loaded = - db.getTransportKeys(txn, transportId); - // Rotate the keys to the current rotation period - RotationResult rotationResult = rotateKeys(loaded, now); - // Initialise mutable state for all contacts - addKeys(rotationResult.current); - // Write any rotated keys back to the DB - if (!rotationResult.rotated.isEmpty()) - db.updateTransportKeys(txn, rotationResult.rotated); - } finally { - lock.unlock(); - } - // Schedule the next key rotation - scheduleKeyRotation(now); - } - - private RotationResult rotateKeys(Map<ContactId, TransportKeys> keys, - long now) { - RotationResult rotationResult = new RotationResult(); - long rotationPeriod = now / rotationPeriodLength; - for (Entry<ContactId, TransportKeys> e : keys.entrySet()) { - ContactId c = e.getKey(); - TransportKeys k = e.getValue(); - TransportKeys k1 = crypto.rotateTransportKeys(k, rotationPeriod); - if (k1.getRotationPeriod() > k.getRotationPeriod()) - rotationResult.rotated.put(c, k1); - rotationResult.current.put(c, k1); - } - return rotationResult; - } - - // Locking: lock - private void addKeys(Map<ContactId, TransportKeys> m) { - for (Entry<ContactId, TransportKeys> e : m.entrySet()) - addKeys(e.getKey(), new MutableTransportKeys(e.getValue())); - } - - // Locking: lock - private void addKeys(ContactId c, MutableTransportKeys m) { - encodeTags(c, m.getPreviousIncomingKeys()); - encodeTags(c, m.getCurrentIncomingKeys()); - encodeTags(c, m.getNextIncomingKeys()); - outContexts.put(c, m.getCurrentOutgoingKeys()); - keys.put(c, m); - } - - // Locking: lock - private void encodeTags(ContactId c, MutableIncomingKeys inKeys) { - for (long streamNumber : inKeys.getWindow().getUnseen()) { - TagContext tagCtx = new TagContext(c, inKeys, streamNumber); - byte[] tag = new byte[TAG_LENGTH]; - crypto.encodeTag(tag, inKeys.getTagKey(), streamNumber); - inContexts.put(new Bytes(tag), tagCtx); - } - } - - private void scheduleKeyRotation(long now) { - Runnable task = new Runnable() { - @Override - public void run() { - rotateKeys(); - } - }; - long delay = rotationPeriodLength - now % rotationPeriodLength; - scheduler.schedule(task, delay, MILLISECONDS); - } - - private void rotateKeys() { - dbExecutor.execute(new Runnable() { - @Override - public void run() { - try { - Transaction txn = db.startTransaction(false); - try { - rotateKeys(txn); - db.commitTransaction(txn); - } finally { - db.endTransaction(txn); - } - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); - } + void start(Transaction txn) throws DbException; void addContact(Transaction txn, ContactId c, SecretKey master, - long timestamp, boolean alice) throws DbException { - lock.lock(); - try { - // Work out what rotation period the timestamp belongs to - long rotationPeriod = timestamp / rotationPeriodLength; - // Derive the transport keys - TransportKeys k = crypto.deriveTransportKeys(transportId, master, - rotationPeriod, alice); - // Rotate the keys to the current rotation period if necessary - rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength; - k = crypto.rotateTransportKeys(k, rotationPeriod); - // Initialise mutable state for the contact - addKeys(c, new MutableTransportKeys(k)); - // Write the keys back to the DB - db.addTransportKeys(txn, c, k); - } finally { - lock.unlock(); - } - } + long timestamp, boolean alice) throws DbException; - void removeContact(ContactId c) { - lock.lock(); - try { - // Remove mutable state for the contact - Iterator<Entry<Bytes, TagContext>> it = - inContexts.entrySet().iterator(); - while (it.hasNext()) - if (it.next().getValue().contactId.equals(c)) it.remove(); - outContexts.remove(c); - keys.remove(c); - } finally { - lock.unlock(); - } - } + void removeContact(ContactId c); StreamContext getStreamContext(Transaction txn, ContactId c) - throws DbException { - lock.lock(); - try { - // Look up the outgoing keys for the contact - MutableOutgoingKeys outKeys = outContexts.get(c); - if (outKeys == null) return null; - if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null; - // Create a stream context - StreamContext ctx = new StreamContext(c, transportId, - outKeys.getTagKey(), outKeys.getHeaderKey(), - outKeys.getStreamCounter()); - // Increment the stream counter and write it back to the DB - outKeys.incrementStreamCounter(); - db.incrementStreamCounter(txn, c, transportId, - outKeys.getRotationPeriod()); - return ctx; - } finally { - lock.unlock(); - } - } + throws DbException; StreamContext getStreamContext(Transaction txn, byte[] tag) - throws DbException { - lock.lock(); - try { - // Look up the incoming keys for the tag - TagContext tagCtx = inContexts.remove(new Bytes(tag)); - if (tagCtx == null) return null; - MutableIncomingKeys inKeys = tagCtx.inKeys; - // Create a stream context - StreamContext ctx = new StreamContext(tagCtx.contactId, transportId, - inKeys.getTagKey(), inKeys.getHeaderKey(), - tagCtx.streamNumber); - // Update the reordering window - ReorderingWindow window = inKeys.getWindow(); - Change change = window.setSeen(tagCtx.streamNumber); - // Add tags for any stream numbers added to the window - for (long streamNumber : change.getAdded()) { - byte[] addTag = new byte[TAG_LENGTH]; - crypto.encodeTag(addTag, inKeys.getTagKey(), streamNumber); - inContexts.put(new Bytes(addTag), new TagContext( - tagCtx.contactId, inKeys, streamNumber)); - } - // Remove tags for any stream numbers removed from the window - for (long streamNumber : change.getRemoved()) { - if (streamNumber == tagCtx.streamNumber) continue; - byte[] removeTag = new byte[TAG_LENGTH]; - crypto.encodeTag(removeTag, inKeys.getTagKey(), streamNumber); - inContexts.remove(new Bytes(removeTag)); - } - // Write the window back to the DB - db.setReorderingWindow(txn, tagCtx.contactId, transportId, - inKeys.getRotationPeriod(), window.getBase(), - window.getBitmap()); - return ctx; - } finally { - lock.unlock(); - } - } - - private void rotateKeys(Transaction txn) throws DbException { - long now = clock.currentTimeMillis(); - lock.lock(); - try { - // Rotate the keys to the current rotation period - Map<ContactId, TransportKeys> snapshot = - new HashMap<ContactId, TransportKeys>(); - for (Entry<ContactId, MutableTransportKeys> e : keys.entrySet()) - snapshot.put(e.getKey(), e.getValue().snapshot()); - RotationResult rotationResult = rotateKeys(snapshot, now); - // Rebuild the mutable state for all contacts - inContexts.clear(); - outContexts.clear(); - keys.clear(); - addKeys(rotationResult.current); - // Write any rotated keys back to the DB - if (!rotationResult.rotated.isEmpty()) - db.updateTransportKeys(txn, rotationResult.rotated); - } finally { - lock.unlock(); - } - // Schedule the next key rotation - scheduleKeyRotation(now); - } - - private static class TagContext { - - private final ContactId contactId; - private final MutableIncomingKeys inKeys; - private final long streamNumber; - - private TagContext(ContactId contactId, MutableIncomingKeys inKeys, - long streamNumber) { - this.contactId = contactId; - this.inKeys = inKeys; - this.streamNumber = streamNumber; - } - } - - private static class RotationResult { - - private final Map<ContactId, TransportKeys> current, rotated; + throws DbException; - private RotationResult() { - current = new HashMap<ContactId, TransportKeys>(); - rotated = new HashMap<ContactId, TransportKeys>(); - } - } } diff --git a/briar-core/src/org/briarproject/transport/TransportKeyManagerFactory.java b/briar-core/src/org/briarproject/transport/TransportKeyManagerFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..ca1021a0c1e5628dd70e6d481434bacd2c33f4d8 --- /dev/null +++ b/briar-core/src/org/briarproject/transport/TransportKeyManagerFactory.java @@ -0,0 +1,10 @@ +package org.briarproject.transport; + +import org.briarproject.api.TransportId; + +interface TransportKeyManagerFactory { + + TransportKeyManager createTransportKeyManager(TransportId transportId, + long maxLatency); + +} diff --git a/briar-core/src/org/briarproject/transport/TransportKeyManagerFactoryImpl.java b/briar-core/src/org/briarproject/transport/TransportKeyManagerFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..dbc05778ba6ab5cd828dbc0c6515043420254a2b --- /dev/null +++ b/briar-core/src/org/briarproject/transport/TransportKeyManagerFactoryImpl.java @@ -0,0 +1,41 @@ +package org.briarproject.transport; + +import org.briarproject.api.TransportId; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.DatabaseExecutor; +import org.briarproject.api.system.Clock; + +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; + +import javax.inject.Inject; + +class TransportKeyManagerFactoryImpl implements + TransportKeyManagerFactory { + + private final DatabaseComponent db; + private final CryptoComponent crypto; + private final Executor dbExecutor; + private final ScheduledExecutorService scheduler; + private final Clock clock; + + @Inject + TransportKeyManagerFactoryImpl(DatabaseComponent db, CryptoComponent crypto, + @DatabaseExecutor Executor dbExecutor, + ScheduledExecutorService scheduler, Clock clock) { + this.db = db; + this.crypto = crypto; + this.dbExecutor = dbExecutor; + this.scheduler = scheduler; + this.clock = clock; + } + + @Override + public TransportKeyManager createTransportKeyManager( + TransportId transportId, long maxLatency) { + return new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler, + clock, transportId, maxLatency); + } + +} diff --git a/briar-core/src/org/briarproject/transport/TransportKeyManagerImpl.java b/briar-core/src/org/briarproject/transport/TransportKeyManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..3afd0ef928b8005c3e1d98153c17c9d4d49bc001 --- /dev/null +++ b/briar-core/src/org/briarproject/transport/TransportKeyManagerImpl.java @@ -0,0 +1,308 @@ +package org.briarproject.transport; + +import org.briarproject.api.Bytes; +import org.briarproject.api.TransportId; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.crypto.SecretKey; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.system.Clock; +import org.briarproject.api.transport.StreamContext; +import org.briarproject.api.transport.TransportKeys; +import org.briarproject.transport.ReorderingWindow.Change; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Logger; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.logging.Level.WARNING; +import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE; +import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH; +import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED; + +class TransportKeyManagerImpl implements TransportKeyManager { + + private static final Logger LOG = + Logger.getLogger(TransportKeyManagerImpl.class.getName()); + + private final DatabaseComponent db; + private final CryptoComponent crypto; + private final Executor dbExecutor; + private final ScheduledExecutorService scheduler; + private final Clock clock; + private final TransportId transportId; + private final long rotationPeriodLength; + private final ReentrantLock lock; + + // The following are locking: lock + private final Map<Bytes, TagContext> inContexts; + private final Map<ContactId, MutableOutgoingKeys> outContexts; + private final Map<ContactId, MutableTransportKeys> keys; + + TransportKeyManagerImpl(DatabaseComponent db, CryptoComponent crypto, + Executor dbExecutor, ScheduledExecutorService scheduler, + Clock clock, TransportId transportId, long maxLatency) { + this.db = db; + this.crypto = crypto; + this.dbExecutor = dbExecutor; + this.scheduler = scheduler; + this.clock = clock; + this.transportId = transportId; + rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; + lock = new ReentrantLock(); + inContexts = new HashMap<Bytes, TagContext>(); + outContexts = new HashMap<ContactId, MutableOutgoingKeys>(); + keys = new HashMap<ContactId, MutableTransportKeys>(); + } + + @Override + public void start(Transaction txn) throws DbException { + long now = clock.currentTimeMillis(); + lock.lock(); + try { + // Load the transport keys from the DB + Map<ContactId, TransportKeys> loaded = + db.getTransportKeys(txn, transportId); + // Rotate the keys to the current rotation period + RotationResult rotationResult = rotateKeys(loaded, now); + // Initialise mutable state for all contacts + addKeys(rotationResult.current); + // Write any rotated keys back to the DB + if (!rotationResult.rotated.isEmpty()) + db.updateTransportKeys(txn, rotationResult.rotated); + } finally { + lock.unlock(); + } + // Schedule the next key rotation + scheduleKeyRotation(now); + } + + private RotationResult rotateKeys(Map<ContactId, TransportKeys> keys, + long now) { + RotationResult rotationResult = new RotationResult(); + long rotationPeriod = now / rotationPeriodLength; + for (Entry<ContactId, TransportKeys> e : keys.entrySet()) { + ContactId c = e.getKey(); + TransportKeys k = e.getValue(); + TransportKeys k1 = crypto.rotateTransportKeys(k, rotationPeriod); + if (k1.getRotationPeriod() > k.getRotationPeriod()) + rotationResult.rotated.put(c, k1); + rotationResult.current.put(c, k1); + } + return rotationResult; + } + + // Locking: lock + private void addKeys(Map<ContactId, TransportKeys> m) { + for (Entry<ContactId, TransportKeys> e : m.entrySet()) + addKeys(e.getKey(), new MutableTransportKeys(e.getValue())); + } + + // Locking: lock + private void addKeys(ContactId c, MutableTransportKeys m) { + encodeTags(c, m.getPreviousIncomingKeys()); + encodeTags(c, m.getCurrentIncomingKeys()); + encodeTags(c, m.getNextIncomingKeys()); + outContexts.put(c, m.getCurrentOutgoingKeys()); + keys.put(c, m); + } + + // Locking: lock + private void encodeTags(ContactId c, MutableIncomingKeys inKeys) { + for (long streamNumber : inKeys.getWindow().getUnseen()) { + TagContext tagCtx = new TagContext(c, inKeys, streamNumber); + byte[] tag = new byte[TAG_LENGTH]; + crypto.encodeTag(tag, inKeys.getTagKey(), streamNumber); + inContexts.put(new Bytes(tag), tagCtx); + } + } + + private void scheduleKeyRotation(long now) { + Runnable task = new Runnable() { + @Override + public void run() { + rotateKeys(); + } + }; + long delay = rotationPeriodLength - now % rotationPeriodLength; + scheduler.schedule(task, delay, MILLISECONDS); + } + + private void rotateKeys() { + dbExecutor.execute(new Runnable() { + @Override + public void run() { + try { + Transaction txn = db.startTransaction(false); + try { + rotateKeys(txn); + db.commitTransaction(txn); + } finally { + db.endTransaction(txn); + } + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } + } + }); + } + + @Override + public void addContact(Transaction txn, ContactId c, SecretKey master, + long timestamp, boolean alice) throws DbException { + lock.lock(); + try { + // Work out what rotation period the timestamp belongs to + long rotationPeriod = timestamp / rotationPeriodLength; + // Derive the transport keys + TransportKeys k = crypto.deriveTransportKeys(transportId, master, + rotationPeriod, alice); + // Rotate the keys to the current rotation period if necessary + rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength; + k = crypto.rotateTransportKeys(k, rotationPeriod); + // Initialise mutable state for the contact + addKeys(c, new MutableTransportKeys(k)); + // Write the keys back to the DB + db.addTransportKeys(txn, c, k); + } finally { + lock.unlock(); + } + } + + @Override + public void removeContact(ContactId c) { + lock.lock(); + try { + // Remove mutable state for the contact + Iterator<Entry<Bytes, TagContext>> it = + inContexts.entrySet().iterator(); + while (it.hasNext()) + if (it.next().getValue().contactId.equals(c)) it.remove(); + outContexts.remove(c); + keys.remove(c); + } finally { + lock.unlock(); + } + } + + @Override + public StreamContext getStreamContext(Transaction txn, ContactId c) + throws DbException { + lock.lock(); + try { + // Look up the outgoing keys for the contact + MutableOutgoingKeys outKeys = outContexts.get(c); + if (outKeys == null) return null; + if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null; + // Create a stream context + StreamContext ctx = new StreamContext(c, transportId, + outKeys.getTagKey(), outKeys.getHeaderKey(), + outKeys.getStreamCounter()); + // Increment the stream counter and write it back to the DB + outKeys.incrementStreamCounter(); + db.incrementStreamCounter(txn, c, transportId, + outKeys.getRotationPeriod()); + return ctx; + } finally { + lock.unlock(); + } + } + + @Override + public StreamContext getStreamContext(Transaction txn, byte[] tag) + throws DbException { + lock.lock(); + try { + // Look up the incoming keys for the tag + TagContext tagCtx = inContexts.remove(new Bytes(tag)); + if (tagCtx == null) return null; + MutableIncomingKeys inKeys = tagCtx.inKeys; + // Create a stream context + StreamContext ctx = new StreamContext(tagCtx.contactId, transportId, + inKeys.getTagKey(), inKeys.getHeaderKey(), + tagCtx.streamNumber); + // Update the reordering window + ReorderingWindow window = inKeys.getWindow(); + Change change = window.setSeen(tagCtx.streamNumber); + // Add tags for any stream numbers added to the window + for (long streamNumber : change.getAdded()) { + byte[] addTag = new byte[TAG_LENGTH]; + crypto.encodeTag(addTag, inKeys.getTagKey(), streamNumber); + inContexts.put(new Bytes(addTag), new TagContext( + tagCtx.contactId, inKeys, streamNumber)); + } + // Remove tags for any stream numbers removed from the window + for (long streamNumber : change.getRemoved()) { + if (streamNumber == tagCtx.streamNumber) continue; + byte[] removeTag = new byte[TAG_LENGTH]; + crypto.encodeTag(removeTag, inKeys.getTagKey(), streamNumber); + inContexts.remove(new Bytes(removeTag)); + } + // Write the window back to the DB + db.setReorderingWindow(txn, tagCtx.contactId, transportId, + inKeys.getRotationPeriod(), window.getBase(), + window.getBitmap()); + return ctx; + } finally { + lock.unlock(); + } + } + + private void rotateKeys(Transaction txn) throws DbException { + long now = clock.currentTimeMillis(); + lock.lock(); + try { + // Rotate the keys to the current rotation period + Map<ContactId, TransportKeys> snapshot = + new HashMap<ContactId, TransportKeys>(); + for (Entry<ContactId, MutableTransportKeys> e : keys.entrySet()) + snapshot.put(e.getKey(), e.getValue().snapshot()); + RotationResult rotationResult = rotateKeys(snapshot, now); + // Rebuild the mutable state for all contacts + inContexts.clear(); + outContexts.clear(); + keys.clear(); + addKeys(rotationResult.current); + // Write any rotated keys back to the DB + if (!rotationResult.rotated.isEmpty()) + db.updateTransportKeys(txn, rotationResult.rotated); + } finally { + lock.unlock(); + } + // Schedule the next key rotation + scheduleKeyRotation(now); + } + + private static class TagContext { + + private final ContactId contactId; + private final MutableIncomingKeys inKeys; + private final long streamNumber; + + private TagContext(ContactId contactId, MutableIncomingKeys inKeys, + long streamNumber) { + this.contactId = contactId; + this.inKeys = inKeys; + this.streamNumber = streamNumber; + } + } + + private static class RotationResult { + + private final Map<ContactId, TransportKeys> current, rotated; + + private RotationResult() { + current = new HashMap<ContactId, TransportKeys>(); + rotated = new HashMap<ContactId, TransportKeys>(); + } + } +} diff --git a/briar-core/src/org/briarproject/transport/TransportModule.java b/briar-core/src/org/briarproject/transport/TransportModule.java index 4c614ddc69d7505dd2e19879eda73942e96f7fb9..461e1d8dcb91fd883d01d6e8eabf8da1312d4aef 100644 --- a/briar-core/src/org/briarproject/transport/TransportModule.java +++ b/briar-core/src/org/briarproject/transport/TransportModule.java @@ -34,6 +34,12 @@ public class TransportModule { return new StreamWriterFactoryImpl(streamEncrypterFactory); } + @Provides + TransportKeyManagerFactory provideTransportKeyManagerFactory( + TransportKeyManagerFactoryImpl transportKeyManagerFactory) { + return transportKeyManagerFactory; + } + @Provides @Singleton KeyManager provideKeyManager(LifecycleManager lifecycleManager, diff --git a/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java b/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java index d55771fcfd00d09f4e5ae479ce022947d80af830..3ac7d5f1c75499e00a6be260b13baafc0dc5d35e 100644 --- a/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java +++ b/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java @@ -1,14 +1,203 @@ package org.briarproject.transport; import org.briarproject.BriarTestCase; +import org.briarproject.api.TransportId; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.crypto.SecretKey; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.event.ContactRemovedEvent; +import org.briarproject.api.event.ContactStatusChangedEvent; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.plugins.PluginConfig; +import org.briarproject.api.plugins.simplex.SimplexPluginFactory; +import org.briarproject.api.transport.StreamContext; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.jmock.lib.concurrent.DeterministicExecutor; +import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.fail; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +import static org.briarproject.TestUtils.getRandomBytes; +import static org.briarproject.TestUtils.getRandomId; +import static org.briarproject.TestUtils.getSecretKey; +import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH; +import static org.junit.Assert.assertEquals; public class KeyManagerImplTest extends BriarTestCase { + private final Mockery context = new Mockery(); + private final KeyManagerImpl keyManager; + private final DatabaseComponent db = context.mock(DatabaseComponent.class); + private final PluginConfig pluginConfig = context.mock(PluginConfig.class); + private final TransportKeyManagerFactory transportKeyManagerFactory = + context.mock(TransportKeyManagerFactory.class); + private final TransportKeyManager transportKeyManager = + context.mock(TransportKeyManager.class); + private final DeterministicExecutor executor = new DeterministicExecutor(); + private final Transaction txn = new Transaction(null, false); + private final ContactId contactId = new ContactId(42); + private final ContactId inactiveContactId = new ContactId(43); + private final TransportId transportId = new TransportId("tId"); + private final TransportId unknownTransportId = new TransportId("id"); + private final StreamContext streamContext = + new StreamContext(contactId, transportId, getSecretKey(), + getSecretKey(), 1); + private final byte[] tag = getRandomBytes(TAG_LENGTH); + + public KeyManagerImplTest() { + keyManager = new KeyManagerImpl(db, executor, pluginConfig, + transportKeyManagerFactory); + } + + @Before + public void testStartService() throws Exception { + final Transaction txn = new Transaction(null, false); + AuthorId remoteAuthorId = new AuthorId(getRandomId()); + Author remoteAuthor = new Author(remoteAuthorId, "author", + getRandomBytes(42)); + AuthorId localAuthorId = new AuthorId(getRandomId()); + final Collection<Contact> contacts = new ArrayList<>(); + contacts.add(new Contact(contactId, remoteAuthor, localAuthorId, true, + true)); + contacts.add(new Contact(inactiveContactId, remoteAuthor, localAuthorId, + true, false)); + final SimplexPluginFactory pluginFactory = + context.mock(SimplexPluginFactory.class); + final Collection<SimplexPluginFactory> factories = Collections + .singletonList(pluginFactory); + final int maxLatency = 1337; + + context.checking(new Expectations() {{ + oneOf(pluginConfig).getSimplexFactories(); + will(returnValue(factories)); + oneOf(pluginFactory).getId(); + will(returnValue(transportId)); + oneOf(pluginFactory).getMaxLatency(); + will(returnValue(maxLatency)); + oneOf(db).addTransport(txn, transportId, maxLatency); + oneOf(transportKeyManagerFactory) + .createTransportKeyManager(transportId, maxLatency); + will(returnValue(transportKeyManager)); + oneOf(pluginConfig).getDuplexFactories(); + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(db).getContacts(txn); + will(returnValue(contacts)); + oneOf(transportKeyManager).start(txn); + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + keyManager.startService(); + } + + @Test + public void testAddContact() throws Exception { + final SecretKey secretKey = getSecretKey(); + final long timestamp = 42L; + final boolean alice = true; + + context.checking(new Expectations() {{ + oneOf(transportKeyManager) + .addContact(txn, contactId, secretKey, timestamp, alice); + }}); + + keyManager.addContact(txn, contactId, secretKey, timestamp, alice); + context.assertIsSatisfied(); + } + + @Test + public void testGetStreamContextForInactiveContact() throws Exception { + assertEquals(null, + keyManager.getStreamContext(inactiveContactId, transportId)); + } + + @Test + public void testGetStreamContextForUnknownTransport() throws Exception { + assertEquals(null, keyManager + .getStreamContext(contactId, unknownTransportId)); + } + + @Test + public void testGetStreamContextForContact() throws Exception { + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(transportKeyManager).getStreamContext(txn, contactId); + will(returnValue(streamContext)); + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + assertEquals(streamContext, + keyManager.getStreamContext(contactId, transportId)); + context.assertIsSatisfied(); + } + + @Test + public void testGetStreamContextForTagAndUnknownTransport() + throws Exception { + assertEquals(null, + keyManager.getStreamContext(unknownTransportId, tag)); + } + + @Test + public void testGetStreamContextForTag() throws Exception { + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(transportKeyManager).getStreamContext(txn, tag); + will(returnValue(streamContext)); + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + assertEquals(streamContext, + keyManager.getStreamContext(transportId, tag)); + context.assertIsSatisfied(); + } + @Test - public void testUnitTestsExist() { - fail(); // FIXME: Write tests + public void testContactRemovedEvent() throws Exception { + ContactRemovedEvent event = new ContactRemovedEvent(contactId); + + context.checking(new Expectations() {{ + oneOf(transportKeyManager).removeContact(contactId); + }}); + + keyManager.eventOccurred(event); + executor.runUntilIdle(); + assertEquals(null, keyManager.getStreamContext(contactId, transportId)); + + context.assertIsSatisfied(); + } + + @Test + public void testContactStatusChangedEvent() throws Exception { + ContactStatusChangedEvent event = + new ContactStatusChangedEvent(inactiveContactId, true); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(transportKeyManager).getStreamContext(txn, inactiveContactId); + will(returnValue(streamContext)); + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + keyManager.eventOccurred(event); + assertEquals(streamContext, + keyManager.getStreamContext(inactiveContactId, transportId)); + + context.assertIsSatisfied(); } + } diff --git a/briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java b/briar-tests/src/org/briarproject/transport/TransportKeyManagerImplTest.java similarity index 96% rename from briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java rename to briar-tests/src/org/briarproject/transport/TransportKeyManagerImplTest.java index b97d1786c3ea0f2d47b76c405626be4cdddb527d..0f039dfdac7b73d56d920fe6321ceae13df9f0ec 100644 --- a/briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java +++ b/briar-tests/src/org/briarproject/transport/TransportKeyManagerImplTest.java @@ -40,7 +40,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -public class TransportKeyManagerTest extends BriarTestCase { +public class TransportKeyManagerImplTest extends BriarTestCase { private final TransportId transportId = new TransportId("id"); private final long maxLatency = 30 * 1000; // 30 seconds @@ -96,7 +96,8 @@ public class TransportKeyManagerTest extends BriarTestCase { with(rotationPeriodLength - 1), with(MILLISECONDS)); }}); - TransportKeyManager transportKeyManager = new TransportKeyManager(db, + TransportKeyManager + transportKeyManager = new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler, clock, transportId, maxLatency); transportKeyManager.start(txn); @@ -138,7 +139,8 @@ public class TransportKeyManagerTest extends BriarTestCase { oneOf(db).addTransportKeys(txn, contactId, rotated); }}); - TransportKeyManager transportKeyManager = new TransportKeyManager(db, + TransportKeyManager + transportKeyManager = new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler, clock, transportId, maxLatency); // The timestamp is 1 ms before the start of rotation period 1000 long timestamp = rotationPeriodLength * 1000 - 1; @@ -161,7 +163,8 @@ public class TransportKeyManagerTest extends BriarTestCase { final Transaction txn = new Transaction(null, false); - TransportKeyManager transportKeyManager = new TransportKeyManager(db, + TransportKeyManager + transportKeyManager = new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler, clock, transportId, maxLatency); assertNull(transportKeyManager.getStreamContext(txn, contactId)); @@ -205,7 +208,8 @@ public class TransportKeyManagerTest extends BriarTestCase { oneOf(db).addTransportKeys(txn, contactId, transportKeys); }}); - TransportKeyManager transportKeyManager = new TransportKeyManager(db, + TransportKeyManager + transportKeyManager = new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler, clock, transportId, maxLatency); // The timestamp is at the start of rotation period 1000 long timestamp = rotationPeriodLength * 1000; @@ -254,7 +258,8 @@ public class TransportKeyManagerTest extends BriarTestCase { oneOf(db).incrementStreamCounter(txn, contactId, transportId, 1000); }}); - TransportKeyManager transportKeyManager = new TransportKeyManager(db, + TransportKeyManager + transportKeyManager = new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler, clock, transportId, maxLatency); // The timestamp is at the start of rotation period 1000 long timestamp = rotationPeriodLength * 1000; @@ -310,7 +315,8 @@ public class TransportKeyManagerTest extends BriarTestCase { oneOf(db).addTransportKeys(txn, contactId, transportKeys); }}); - TransportKeyManager transportKeyManager = new TransportKeyManager(db, + TransportKeyManager + transportKeyManager = new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler, clock, transportId, maxLatency); // The timestamp is at the start of rotation period 1000 long timestamp = rotationPeriodLength * 1000; @@ -365,7 +371,8 @@ public class TransportKeyManagerTest extends BriarTestCase { 1, new byte[REORDERING_WINDOW_SIZE / 8]); }}); - TransportKeyManager transportKeyManager = new TransportKeyManager(db, + TransportKeyManager + transportKeyManager = new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler, clock, transportId, maxLatency); // The timestamp is at the start of rotation period 1000 long timestamp = rotationPeriodLength * 1000; @@ -456,7 +463,8 @@ public class TransportKeyManagerTest extends BriarTestCase { oneOf(db).endTransaction(txn1); }}); - TransportKeyManager transportKeyManager = new TransportKeyManager(db, + TransportKeyManager + transportKeyManager = new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler, clock, transportId, maxLatency); transportKeyManager.start(txn);