Merge branch '1556-key-manager-methods-for-pending-contacts' into 'master'

Add key manager methods for pending contacts

Closes #1556

See merge request briar/briar!1089
This commit is contained in:
Torsten Grote
2019-05-15 19:57:32 +00:00
16 changed files with 495 additions and 105 deletions

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.util.StringUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
/** /**
@@ -38,7 +39,7 @@ public class Bytes implements Comparable<Bytes> {
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(@Nullable Object o) {
return o instanceof Bytes && Arrays.equals(bytes, ((Bytes) o).bytes); return o instanceof Bytes && Arrays.equals(bytes, ((Bytes) o).bytes);
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
@@ -28,7 +29,7 @@ public class ContactId {
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(@Nullable Object o) {
return o instanceof ContactId && id == ((ContactId) o).id; return o instanceof ContactId && id == ((ContactId) o).id;
} }
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
/** /**
@@ -17,9 +18,8 @@ public class PendingContactId extends UniqueId {
super(id); super(id);
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(@Nullable Object o) {
return o instanceof PendingContactId && super.equals(o); return o instanceof PendingContactId && super.equals(o);
} }
} }

View File

@@ -6,10 +6,20 @@ import javax.annotation.Nullable;
public class NullSafety { public class NullSafety {
/** /**
* Stand-in for `Objects.requireNonNull()`. * Stand-in for {@code Objects.requireNonNull()}.
*/ */
public static <T> T requireNonNull(@Nullable T t) { public static <T> T requireNonNull(@Nullable T t) {
if (t == null) throw new NullPointerException(); if (t == null) throw new NullPointerException();
return t; return t;
} }
/**
* Checks that exactly one of the arguments is null.
*
* @throws AssertionError If both or neither of the arguments are null
*/
public static void requireExactlyOneNull(@Nullable Object a,
@Nullable Object b) {
if ((a == null) == (b == null)) throw new AssertionError();
}
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api.transport; package org.briarproject.bramble.api.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -18,17 +19,45 @@ public interface KeyManager {
/** /**
* Informs the key manager that a new contact has been added. Derives and * Informs the key manager that a new contact has been added. Derives and
* stores a set of transport keys for communicating with the contact over * stores a set of rotation mode transport keys for communicating with the
* each transport and returns the key set IDs. * contact over each transport and returns the key set IDs.
* <p/> * <p/>
* {@link StreamContext StreamContexts} for the contact can be created * {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned. * after this method has returned.
* *
* @param alice true if the local party is Alice * @param alice True if the local party is Alice
* @param active whether the derived keys can be used for outgoing streams * @param active Whether the derived keys can be used for outgoing streams
*/ */
Map<TransportId, KeySetId> addContact(Transaction txn, ContactId c, Map<TransportId, KeySetId> addContactWithRotationKeys(Transaction txn,
SecretKey rootKey, long timestamp, boolean alice, boolean active) ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException;
/**
* Informs the key manager that a new contact has been added. Derives and
* stores a set of handshake mode transport keys for communicating with the
* contact over each transport and returns the key set IDs.
* <p/>
* {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned.
*
* @param alice True if the local party is Alice
*/
Map<TransportId, KeySetId> addContactWithHandshakeKeys(Transaction txn,
ContactId c, SecretKey rootKey, boolean alice) throws DbException;
/**
* Informs the key manager that a new pending contact has been added.
* Derives and stores a set of handshake mode transport keys for
* communicating with the pending contact over each transport and returns
* the key set IDs.
* <p/>
* {@link StreamContext StreamContexts} for the pending contact can be
* created after this method has returned.
*
* @param alice True if the local party is Alice
*/
Map<TransportId, KeySetId> addPendingContact(Transaction txn,
PendingContactId p, SecretKey rootKey, boolean alice)
throws DbException; throws DbException;
/** /**
@@ -43,15 +72,28 @@ public interface KeyManager {
*/ */
boolean canSendOutgoingStreams(ContactId c, TransportId t); boolean canSendOutgoingStreams(ContactId c, TransportId t);
/**
* Returns true if we have keys that can be used for outgoing streams to
* the given pending contact over the given transport.
*/
boolean canSendOutgoingStreams(PendingContactId p, TransportId t);
/** /**
* Returns a {@link StreamContext} for sending a stream to the given * Returns a {@link StreamContext} for sending a stream to the given
* contact over the given transport, or null if an error occurs or the * contact over the given transport, or null if an error occurs.
* contact does not support the transport.
*/ */
@Nullable @Nullable
StreamContext getStreamContext(ContactId c, TransportId t) StreamContext getStreamContext(ContactId c, TransportId t)
throws DbException; throws DbException;
/**
* Returns a {@link StreamContext} for sending a stream to the given
* pending contact over the given transport, or null if an error occurs.
*/
@Nullable
StreamContext getStreamContext(PendingContactId p, TransportId t)
throws DbException;
/** /**
* Looks up the given tag and returns a {@link StreamContext} for reading * Looks up the given tag and returns a {@link StreamContext} for reading
* from the corresponding stream, or null if an error occurs or the tag was * from the corresponding stream, or null if an error occurs or the tag was

View File

@@ -9,6 +9,8 @@ import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireExactlyOneNull;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class StreamContext { public class StreamContext {
@@ -26,8 +28,7 @@ public class StreamContext {
@Nullable PendingContactId pendingContactId, @Nullable PendingContactId pendingContactId,
TransportId transportId, SecretKey tagKey, SecretKey headerKey, TransportId transportId, SecretKey tagKey, SecretKey headerKey,
long streamNumber, boolean handshakeMode) { long streamNumber, boolean handshakeMode) {
if ((contactId == null) == (pendingContactId == null)) requireExactlyOneNull(contactId, pendingContactId);
throw new IllegalArgumentException();
this.contactId = contactId; this.contactId = contactId;
this.pendingContactId = pendingContactId; this.pendingContactId = pendingContactId;
this.transportId = transportId; this.transportId = transportId;

View File

@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireExactlyOneNull;
/** /**
* A set of keys for communicating with a given contact or pending contact * A set of keys for communicating with a given contact or pending contact
* over a given transport. * over a given transport.
@@ -24,8 +26,7 @@ public class TransportKeySet {
public TransportKeySet(KeySetId keySetId, @Nullable ContactId contactId, public TransportKeySet(KeySetId keySetId, @Nullable ContactId contactId,
@Nullable PendingContactId pendingContactId, TransportKeys keys) { @Nullable PendingContactId pendingContactId, TransportKeys keys) {
if ((contactId == null) == (pendingContactId == null)) requireExactlyOneNull(contactId, pendingContactId);
throw new IllegalArgumentException();
this.keySetId = keySetId; this.keySetId = keySetId;
this.contactId = contactId; this.contactId = contactId;
this.pendingContactId = pendingContactId; this.pendingContactId = pendingContactId;

View File

@@ -70,7 +70,8 @@ class ContactManagerImpl implements ContactManager {
SecretKey rootKey, long timestamp, boolean alice, boolean verified, SecretKey rootKey, long timestamp, boolean alice, boolean verified,
boolean active) throws DbException { boolean active) throws DbException {
ContactId c = db.addContact(txn, remote, local, verified); ContactId c = db.addContact(txn, remote, local, verified);
keyManager.addContact(txn, c, rootKey, timestamp, alice, active); keyManager.addContactWithRotationKeys(txn, c, rootKey, timestamp,
alice, active);
Contact contact = db.getContact(txn, c); Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) hook.addingContact(txn, contact); for (ContactHook hook : hooks) hook.addingContact(txn, contact);
return c; return c;

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.transport; package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -28,6 +29,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
@@ -88,14 +90,41 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
} }
@Override @Override
public Map<TransportId, KeySetId> addContact(Transaction txn, public Map<TransportId, KeySetId> addContactWithRotationKeys(
ContactId c, SecretKey rootKey, long timestamp, boolean alice, Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
boolean active) throws DbException { boolean alice, boolean active) throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>(); Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) { for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey(); TransportId t = e.getKey();
TransportKeyManager m = e.getValue(); TransportKeyManager m = e.getValue();
ids.put(t, m.addContact(txn, c, rootKey, timestamp, alice, active)); ids.put(t, m.addContactWithRotationKeys(txn, c, rootKey, timestamp,
alice, active));
}
return ids;
}
@Override
public Map<TransportId, KeySetId> addContactWithHandshakeKeys(
Transaction txn, ContactId c, SecretKey rootKey, boolean alice)
throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey();
TransportKeyManager m = e.getValue();
ids.put(t, m.addContactWithHandshakeKeys(txn, c, rootKey, alice));
}
return ids;
}
@Override
public Map<TransportId, KeySetId> addPendingContact(Transaction txn,
PendingContactId p, SecretKey rootKey, boolean alice)
throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey();
TransportKeyManager m = e.getValue();
ids.put(t, m.addPendingContact(txn, p, rootKey, alice));
} }
return ids; return ids;
} }
@@ -104,13 +133,10 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
public void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys) public void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
throws DbException { throws DbException {
for (Entry<TransportId, KeySetId> e : keys.entrySet()) { for (Entry<TransportId, KeySetId> e : keys.entrySet()) {
TransportId t = e.getKey(); withManager(e.getKey(), m -> {
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()); m.activateKeys(txn, e.getValue());
} return null;
});
} }
} }
@@ -120,28 +146,34 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
return m != null && m.canSendOutgoingStreams(c); return m != null && m.canSendOutgoingStreams(c);
} }
@Override
public boolean canSendOutgoingStreams(PendingContactId p, TransportId t) {
TransportKeyManager m = managers.get(t);
return m != null && m.canSendOutgoingStreams(p);
}
@Override @Override
public StreamContext getStreamContext(ContactId c, TransportId t) public StreamContext getStreamContext(ContactId c, TransportId t)
throws DbException { throws DbException {
TransportKeyManager m = managers.get(t); return withManager(t, m ->
if (m == null) { db.transactionWithNullableResult(false, txn ->
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t); m.getStreamContext(txn, c)));
return null; }
}
return db.transactionWithNullableResult(false, txn -> @Override
m.getStreamContext(txn, c)); public StreamContext getStreamContext(PendingContactId p, TransportId t)
throws DbException {
return withManager(t, m ->
db.transactionWithNullableResult(false, txn ->
m.getStreamContext(txn, p)));
} }
@Override @Override
public StreamContext getStreamContext(TransportId t, byte[] tag) public StreamContext getStreamContext(TransportId t, byte[] tag)
throws DbException { throws DbException {
TransportKeyManager m = managers.get(t); return withManager(t, m ->
if (m == null) { db.transactionWithNullableResult(false, txn ->
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t); m.getStreamContext(txn, tag)));
return null;
}
return db.transactionWithNullableResult(false, txn ->
m.getStreamContext(txn, tag));
} }
@Override @Override
@@ -156,4 +188,20 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
for (TransportKeyManager m : managers.values()) m.removeContact(c); for (TransportKeyManager m : managers.values()) m.removeContact(c);
}); });
} }
@Nullable
private <T> T withManager(TransportId t, ManagerTask<T> task)
throws DbException {
TransportKeyManager m = managers.get(t);
if (m == null) {
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
return null;
}
return task.run(m);
}
private interface ManagerTask<T> {
@Nullable
T run(TransportKeyManager m) throws DbException;
}
} }

View File

@@ -8,6 +8,8 @@ import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireExactlyOneNull;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class MutableTransportKeySet { class MutableTransportKeySet {
@@ -22,8 +24,7 @@ class MutableTransportKeySet {
MutableTransportKeySet(KeySetId keySetId, @Nullable ContactId contactId, MutableTransportKeySet(KeySetId keySetId, @Nullable ContactId contactId,
@Nullable PendingContactId pendingContactId, @Nullable PendingContactId pendingContactId,
MutableTransportKeys keys) { MutableTransportKeys keys) {
if ((contactId == null) == (pendingContactId == null)) requireExactlyOneNull(contactId, pendingContactId);
throw new IllegalArgumentException();
this.keySetId = keySetId; this.keySetId = keySetId;
this.contactId = contactId; this.contactId = contactId;
this.pendingContactId = pendingContactId; this.pendingContactId = pendingContactId;

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.transport; package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -15,19 +16,34 @@ interface TransportKeyManager {
void start(Transaction txn) throws DbException; void start(Transaction txn) throws DbException;
KeySetId addContact(Transaction txn, ContactId c, SecretKey rootKey, KeySetId addContactWithRotationKeys(Transaction txn, ContactId c,
long timestamp, boolean alice, boolean active) throws DbException; SecretKey rootKey, long timestamp, boolean alice, boolean active)
throws DbException;
KeySetId addContactWithHandshakeKeys(Transaction txn, ContactId c,
SecretKey rootKey, boolean alice) throws DbException;
KeySetId addPendingContact(Transaction txn, PendingContactId p,
SecretKey rootKey, boolean alice) throws DbException;
void activateKeys(Transaction txn, KeySetId k) throws DbException; void activateKeys(Transaction txn, KeySetId k) throws DbException;
void removeContact(ContactId c); void removeContact(ContactId c);
void removePendingContact(PendingContactId p);
boolean canSendOutgoingStreams(ContactId c); boolean canSendOutgoingStreams(ContactId c);
boolean canSendOutgoingStreams(PendingContactId p);
@Nullable @Nullable
StreamContext getStreamContext(Transaction txn, ContactId c) StreamContext getStreamContext(Transaction txn, ContactId c)
throws DbException; throws DbException;
@Nullable
StreamContext getStreamContext(Transaction txn, PendingContactId p)
throws DbException;
@Nullable @Nullable
StreamContext getStreamContext(Transaction txn, byte[] tag) StreamContext getStreamContext(Transaction txn, byte[] tag)
throws DbException; throws DbException;

View File

@@ -36,6 +36,7 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireExactlyOneNull;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE; import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
@@ -64,8 +65,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
@GuardedBy("lock") @GuardedBy("lock")
private final Map<Bytes, TagContext> inContexts = new HashMap<>(); private final Map<Bytes, TagContext> inContexts = new HashMap<>();
@GuardedBy("lock") @GuardedBy("lock")
private final Map<ContactId, MutableTransportKeySet> outContexts = private final Map<ContactId, MutableTransportKeySet>
new HashMap<>(); contactOutContexts = new HashMap<>();
@GuardedBy("lock")
private final Map<PendingContactId, MutableTransportKeySet>
pendingContactOutContexts = new HashMap<>();
TransportKeyManagerImpl(DatabaseComponent db, TransportKeyManagerImpl(DatabaseComponent db,
TransportCrypto transportCrypto, Executor dbExecutor, TransportCrypto transportCrypto, Executor dbExecutor,
@@ -133,6 +137,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
private void addKeys(KeySetId keySetId, @Nullable ContactId contactId, private void addKeys(KeySetId keySetId, @Nullable ContactId contactId,
@Nullable PendingContactId pendingContactId, @Nullable PendingContactId pendingContactId,
MutableTransportKeys keys) { MutableTransportKeys keys) {
requireExactlyOneNull(contactId, pendingContactId);
MutableTransportKeySet ks = new MutableTransportKeySet(keySetId, MutableTransportKeySet ks = new MutableTransportKeySet(keySetId,
contactId, pendingContactId, keys); contactId, pendingContactId, keys);
this.keys.put(keySetId, ks); this.keys.put(keySetId, ks);
@@ -162,19 +167,30 @@ class TransportKeyManagerImpl implements TransportKeyManager {
@GuardedBy("lock") @GuardedBy("lock")
private void considerReplacingOutgoingKeys(MutableTransportKeySet ks) { private void considerReplacingOutgoingKeys(MutableTransportKeySet ks) {
// Use the active outgoing keys with the highest key set ID // Use the active outgoing keys with the highest key set ID, preferring
ContactId c = ks.getContactId(); // rotation keys to handshake keys
if (c != null && ks.getKeys().getCurrentOutgoingKeys().isActive()) { if (ks.getKeys().getCurrentOutgoingKeys().isActive()) {
MutableTransportKeySet old = outContexts.get(c); MutableTransportKeySet old = getOutgoingKeySet(ks.getContactId(),
if (old == null || ks.getPendingContactId());
(old.getKeys().isHandshakeMode() && if (old == null || (old.getKeys().isHandshakeMode() &&
!ks.getKeys().isHandshakeMode()) || !ks.getKeys().isHandshakeMode()) ||
old.getKeySetId().getInt() < ks.getKeySetId().getInt()) { old.getKeySetId().getInt() < ks.getKeySetId().getInt()) {
outContexts.put(c, ks); if (ks.getContactId() == null)
pendingContactOutContexts.put(ks.getPendingContactId(), ks);
else contactOutContexts.put(ks.getContactId(), ks);
} }
} }
} }
@GuardedBy("lock")
@Nullable
private MutableTransportKeySet getOutgoingKeySet(@Nullable ContactId c,
@Nullable PendingContactId p) {
requireExactlyOneNull(c, p);
if (c == null) return pendingContactOutContexts.get(p);
else return contactOutContexts.get(c);
}
private void scheduleKeyUpdate(long now) { private void scheduleKeyUpdate(long now) {
long delay = timePeriodLength - now % timePeriodLength; long delay = timePeriodLength - now % timePeriodLength;
scheduler.schedule((Runnable) this::updateKeys, delay, MILLISECONDS); scheduler.schedule((Runnable) this::updateKeys, delay, MILLISECONDS);
@@ -191,8 +207,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
@Override @Override
public KeySetId addContact(Transaction txn, ContactId c, SecretKey rootKey, public KeySetId addContactWithRotationKeys(Transaction txn, ContactId c,
long timestamp, boolean alice, boolean active) throws DbException { SecretKey rootKey, long timestamp, boolean alice, boolean active)
throws DbException {
lock.lock(); lock.lock();
try { try {
// Work out what time period the timestamp belongs to // Work out what time period the timestamp belongs to
@@ -213,6 +230,46 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
} }
@Override
public KeySetId addContactWithHandshakeKeys(Transaction txn, ContactId c,
SecretKey rootKey, boolean alice) throws DbException {
lock.lock();
try {
// Work out what time period we're in
long timePeriod = clock.currentTimeMillis() / timePeriodLength;
// Derive the transport keys
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, timePeriod, alice);
// Write the keys back to the DB
KeySetId keySetId = db.addTransportKeys(txn, c, k);
// Initialise mutable state for the contact
addKeys(keySetId, c, null, new MutableTransportKeys(k));
return keySetId;
} finally {
lock.unlock();
}
}
@Override
public KeySetId addPendingContact(Transaction txn, PendingContactId p,
SecretKey rootKey, boolean alice) throws DbException {
lock.lock();
try {
// Work out what time period we're in
long timePeriod = clock.currentTimeMillis() / timePeriodLength;
// Derive the transport keys
TransportKeys k = transportCrypto.deriveHandshakeKeys(transportId,
rootKey, timePeriod, alice);
// Write the keys back to the DB
KeySetId keySetId = db.addTransportKeys(txn, p, k);
// Initialise mutable state for the pending contact
addKeys(keySetId, null, p, new MutableTransportKeys(k));
return keySetId;
} finally {
lock.unlock();
}
}
@Override @Override
public void activateKeys(Transaction txn, KeySetId k) throws DbException { public void activateKeys(Transaction txn, KeySetId k) throws DbException {
lock.lock(); lock.lock();
@@ -234,8 +291,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
try { try {
// Remove mutable state for the contact // Remove mutable state for the contact
Iterator<TagContext> it = inContexts.values().iterator(); Iterator<TagContext> it = inContexts.values().iterator();
while (it.hasNext()) if (c.equals(it.next().contactId)) it.remove(); while (it.hasNext())
outContexts.remove(c); if (c.equals(it.next().contactId)) it.remove();
contactOutContexts.remove(c);
Iterator<MutableTransportKeySet> it1 = keys.values().iterator(); Iterator<MutableTransportKeySet> it1 = keys.values().iterator();
while (it1.hasNext()) while (it1.hasNext())
if (c.equals(it1.next().getContactId())) it1.remove(); if (c.equals(it1.next().getContactId())) it1.remove();
@@ -245,13 +303,39 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
@Override @Override
public boolean canSendOutgoingStreams(ContactId c) { public void removePendingContact(PendingContactId p) {
lock.lock(); lock.lock();
try { try {
MutableTransportKeySet ks = outContexts.get(c); // Remove mutable state for the pending contact
Iterator<TagContext> it = inContexts.values().iterator();
while (it.hasNext())
if (p.equals(it.next().pendingContactId)) it.remove();
pendingContactOutContexts.remove(p);
Iterator<MutableTransportKeySet> it1 = keys.values().iterator();
while (it1.hasNext())
if (p.equals(it1.next().getPendingContactId())) it1.remove();
} finally {
lock.unlock();
}
}
@Override
public boolean canSendOutgoingStreams(ContactId c) {
return canSendOutgoingStreams(c, null);
}
@Override
public boolean canSendOutgoingStreams(PendingContactId p) {
return canSendOutgoingStreams(null, p);
}
private boolean canSendOutgoingStreams(@Nullable ContactId c,
@Nullable PendingContactId p) {
lock.lock();
try {
MutableTransportKeySet ks = getOutgoingKeySet(c, p);
if (ks == null) return false; if (ks == null) return false;
MutableOutgoingKeys outKeys = MutableOutgoingKeys outKeys = ks.getKeys().getCurrentOutgoingKeys();
ks.getKeys().getCurrentOutgoingKeys();
if (!outKeys.isActive()) throw new AssertionError(); if (!outKeys.isActive()) throw new AssertionError();
return outKeys.getStreamCounter() <= MAX_32_BIT_UNSIGNED; return outKeys.getStreamCounter() <= MAX_32_BIT_UNSIGNED;
} finally { } finally {
@@ -262,17 +346,30 @@ class TransportKeyManagerImpl implements TransportKeyManager {
@Override @Override
public StreamContext getStreamContext(Transaction txn, ContactId c) public StreamContext getStreamContext(Transaction txn, ContactId c)
throws DbException { throws DbException {
return getStreamContext(txn, c, null);
}
@Override
public StreamContext getStreamContext(Transaction txn, PendingContactId p)
throws DbException {
return getStreamContext(txn, null, p);
}
@Nullable
private StreamContext getStreamContext(Transaction txn,
@Nullable ContactId c, @Nullable PendingContactId p)
throws DbException {
lock.lock(); lock.lock();
try { try {
// Look up the outgoing keys for the contact // Look up the outgoing keys for the contact
MutableTransportKeySet ks = outContexts.get(c); MutableTransportKeySet ks = getOutgoingKeySet(c, p);
if (ks == null) return null; if (ks == null) return null;
MutableTransportKeys keys = ks.getKeys(); MutableTransportKeys keys = ks.getKeys();
MutableOutgoingKeys outKeys = keys.getCurrentOutgoingKeys(); MutableOutgoingKeys outKeys = keys.getCurrentOutgoingKeys();
if (!outKeys.isActive()) throw new AssertionError(); if (!outKeys.isActive()) throw new AssertionError();
if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null; if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null;
// Create a stream context // Create a stream context
StreamContext ctx = new StreamContext(c, null, transportId, StreamContext ctx = new StreamContext(c, p, transportId,
outKeys.getTagKey(), outKeys.getHeaderKey(), outKeys.getTagKey(), outKeys.getHeaderKey(),
outKeys.getStreamCounter(), keys.isHandshakeMode()); outKeys.getStreamCounter(), keys.isHandshakeMode());
// Increment the stream counter and write it back to the DB // Increment the stream counter and write it back to the DB
@@ -353,7 +450,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
UpdateResult updateResult = updateKeys(snapshot, now); UpdateResult updateResult = updateKeys(snapshot, now);
// Rebuild the mutable state for all contacts // Rebuild the mutable state for all contacts
inContexts.clear(); inContexts.clear();
outContexts.clear(); contactOutContexts.clear();
pendingContactOutContexts.clear();
keys.clear(); keys.clear();
addKeys(updateResult.current); addKeys(updateResult.current);
// Write any updated keys back to the DB // Write any updated keys back to the DB
@@ -381,6 +479,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
@Nullable PendingContactId pendingContactId, @Nullable PendingContactId pendingContactId,
MutableIncomingKeys inKeys, long streamNumber, MutableIncomingKeys inKeys, long streamNumber,
boolean handshakeMode) { boolean handshakeMode) {
requireExactlyOneNull(contactId, pendingContactId);
this.keySetId = keySetId; this.keySetId = keySetId;
this.contactId = contactId; this.contactId = contactId;
this.pendingContactId = pendingContactId; this.pendingContactId = pendingContactId;

View File

@@ -73,8 +73,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
oneOf(db).transactionWithResult(with(false), withDbCallable(txn)); oneOf(db).transactionWithResult(with(false), withDbCallable(txn));
oneOf(db).addContact(txn, remote, local, verified); oneOf(db).addContact(txn, remote, local, verified);
will(returnValue(contactId)); will(returnValue(contactId));
oneOf(keyManager).addContact(txn, contactId, rootKey, timestamp, oneOf(keyManager).addContactWithRotationKeys(txn, contactId,
alice, active); rootKey, timestamp, alice, active);
oneOf(db).getContact(txn, contactId); oneOf(db).getContact(txn, contactId);
will(returnValue(contact)); will(returnValue(contact));
}}); }});

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.transport; package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -26,6 +27,7 @@ import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -43,11 +45,17 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
private final DeterministicExecutor executor = new DeterministicExecutor(); private final DeterministicExecutor executor = new DeterministicExecutor();
private final Transaction txn = new Transaction(null, false); private final Transaction txn = new Transaction(null, false);
private final ContactId contactId = getContactId(); private final ContactId contactId = getContactId();
private final PendingContactId pendingContactId =
new PendingContactId(getRandomId());
private final KeySetId keySetId = new KeySetId(345); private final KeySetId keySetId = new KeySetId(345);
private final TransportId transportId = getTransportId(); private final TransportId transportId = getTransportId();
private final TransportId unknownTransportId = getTransportId(); private final TransportId unknownTransportId = getTransportId();
private final StreamContext streamContext = new StreamContext(contactId, private final StreamContext contactStreamContext =
null, transportId, getSecretKey(), getSecretKey(), 1, false); new StreamContext(contactId, null, transportId, getSecretKey(),
getSecretKey(), 1, false);
private final StreamContext pendingContactStreamContext =
new StreamContext(null, pendingContactId, transportId,
getSecretKey(), getSecretKey(), 1, true);
private final byte[] tag = getRandomBytes(TAG_LENGTH); private final byte[] tag = getRandomBytes(TAG_LENGTH);
private final Random random = new Random(); private final Random random = new Random();
@@ -83,41 +91,94 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testAddContact() throws Exception { public void testAddContactWithRotationModeKeys() throws Exception {
SecretKey secretKey = getSecretKey(); SecretKey secretKey = getSecretKey();
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
boolean alice = random.nextBoolean(); boolean alice = random.nextBoolean();
boolean active = random.nextBoolean(); boolean active = random.nextBoolean();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportKeyManager).addContact(txn, contactId, secretKey, oneOf(transportKeyManager).addContactWithRotationKeys(txn,
timestamp, alice, active); contactId, secretKey, timestamp, alice, active);
will(returnValue(keySetId)); will(returnValue(keySetId));
}}); }});
Map<TransportId, KeySetId> ids = keyManager.addContact(txn, contactId, Map<TransportId, KeySetId> ids = keyManager.addContactWithRotationKeys(
secretKey, timestamp, alice, active); txn, contactId, secretKey, timestamp, alice, active);
assertEquals(singletonMap(transportId, keySetId), ids); assertEquals(singletonMap(transportId, keySetId), ids);
} }
@Test @Test
public void testGetStreamContextForUnknownTransport() throws Exception { public void testAddContactWithHandshakeModeKeys() throws Exception {
SecretKey secretKey = getSecretKey();
boolean alice = random.nextBoolean();
context.checking(new Expectations() {{
oneOf(transportKeyManager).addContactWithHandshakeKeys(
txn, contactId, secretKey, alice);
will(returnValue(keySetId));
}});
Map<TransportId, KeySetId> ids = keyManager.addContactWithHandshakeKeys(
txn, contactId, secretKey, alice);
assertEquals(singletonMap(transportId, keySetId), ids);
}
@Test
public void testAddPendingContact() throws Exception {
SecretKey secretKey = getSecretKey();
boolean alice = random.nextBoolean();
context.checking(new Expectations() {{
oneOf(transportKeyManager).addPendingContact(txn, pendingContactId,
secretKey, alice);
will(returnValue(keySetId));
}});
Map<TransportId, KeySetId> ids = keyManager.addPendingContact(txn,
pendingContactId, secretKey, alice);
assertEquals(singletonMap(transportId, keySetId), ids);
}
@Test
public void testGetStreamContextForContactWithUnknownTransport()
throws Exception {
assertNull(keyManager.getStreamContext(contactId, unknownTransportId)); assertNull(keyManager.getStreamContext(contactId, unknownTransportId));
} }
@Test
public void testGetStreamContextForPendingContactWithUnknownTransport()
throws Exception {
assertNull(keyManager.getStreamContext(pendingContactId,
unknownTransportId));
}
@Test @Test
public void testGetStreamContextForContact() throws Exception { public void testGetStreamContextForContact() throws Exception {
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(false), oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(txn)); withNullableDbCallable(txn));
oneOf(transportKeyManager).getStreamContext(txn, contactId); oneOf(transportKeyManager).getStreamContext(txn, contactId);
will(returnValue(streamContext)); will(returnValue(contactStreamContext));
}}); }});
assertEquals(streamContext, assertEquals(contactStreamContext,
keyManager.getStreamContext(contactId, transportId)); keyManager.getStreamContext(contactId, transportId));
} }
@Test
public void testGetStreamContextForPendingContact() throws Exception {
context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(txn));
oneOf(transportKeyManager).getStreamContext(txn, pendingContactId);
will(returnValue(pendingContactStreamContext));
}});
assertEquals(pendingContactStreamContext,
keyManager.getStreamContext(pendingContactId, transportId));
}
@Test @Test
public void testGetStreamContextForTagAndUnknownTransport() public void testGetStreamContextForTagAndUnknownTransport()
throws Exception { throws Exception {
@@ -130,10 +191,10 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
oneOf(db).transactionWithNullableResult(with(false), oneOf(db).transactionWithNullableResult(with(false),
withNullableDbCallable(txn)); withNullableDbCallable(txn));
oneOf(transportKeyManager).getStreamContext(txn, tag); oneOf(transportKeyManager).getStreamContext(txn, tag);
will(returnValue(streamContext)); will(returnValue(contactStreamContext));
}}); }});
assertEquals(streamContext, assertEquals(contactStreamContext,
keyManager.getStreamContext(transportId, tag)); keyManager.getStreamContext(transportId, tag));
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.transport; package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto; import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -37,6 +38,7 @@ import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE; import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED; import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
@@ -61,6 +63,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
private final long timePeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; private final long timePeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
private final ContactId contactId = getContactId(); private final ContactId contactId = getContactId();
private final ContactId contactId1 = getContactId(); private final ContactId contactId1 = getContactId();
private final PendingContactId pendingContactId =
new PendingContactId(getRandomId());
private final KeySetId keySetId = new KeySetId(345); private final KeySetId keySetId = new KeySetId(345);
private final KeySetId keySetId1 = new KeySetId(456); private final KeySetId keySetId1 = new KeySetId(456);
private final SecretKey tagKey = getSecretKey(); private final SecretKey tagKey = getSecretKey();
@@ -70,14 +74,15 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testKeysAreUpdatedAtStartup() throws Exception { public void testKeysAreUpdatedAtStartup() throws Exception {
TransportKeys shouldUpdate = createTransportKeys(900, 0, true); boolean active = random.nextBoolean();
TransportKeys shouldNotUpdate = createTransportKeys(1000, 0, true); TransportKeys shouldUpdate = createTransportKeys(900, 0, active);
TransportKeys shouldNotUpdate = createTransportKeys(1000, 0, active);
Collection<TransportKeySet> loaded = asList( Collection<TransportKeySet> loaded = asList(
new TransportKeySet(keySetId, contactId, null, shouldUpdate), new TransportKeySet(keySetId, contactId, null, shouldUpdate),
new TransportKeySet(keySetId1, contactId1, null, new TransportKeySet(keySetId1, contactId1, null,
shouldNotUpdate) shouldNotUpdate)
); );
TransportKeys updated = createTransportKeys(1000, 0, true); TransportKeys updated = createTransportKeys(1000, 0, active);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -111,19 +116,22 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
db, transportCrypto, dbExecutor, scheduler, clock, transportId, db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency); maxLatency);
transportKeyManager.start(txn); transportKeyManager.start(txn);
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); assertEquals(active,
transportKeyManager.canSendOutgoingStreams(contactId));
} }
@Test @Test
public void testKeysAreUpdatedWhenAddingContact() throws Exception { public void testRotationKeysAreDerivedAndUpdatedWhenAddingContact()
throws Exception {
boolean alice = random.nextBoolean(); boolean alice = random.nextBoolean();
TransportKeys transportKeys = createTransportKeys(999, 0, true); boolean active = random.nextBoolean();
TransportKeys updated = createTransportKeys(1000, 0, true); TransportKeys transportKeys = createTransportKeys(999, 0, active);
TransportKeys updated = createTransportKeys(1000, 0, active);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportCrypto).deriveRotationKeys(transportId, rootKey, oneOf(transportCrypto).deriveRotationKeys(transportId, rootKey,
999, alice, true); 999, alice, active);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Get the current time (1 ms after start of time period 1000) // Get the current time (1 ms after start of time period 1000)
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
@@ -148,11 +156,83 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is 1 ms before the start of time period 1000 // The timestamp is 1 ms before the start of time period 1000
long timestamp = timePeriodLength * 1000 - 1; long timestamp = timePeriodLength * 1000 - 1;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, true)); txn, contactId, rootKey, timestamp, alice, active));
assertEquals(active,
transportKeyManager.canSendOutgoingStreams(contactId));
}
@Test
public void testHandshakeKeysAreDerivedWhenAddingContact()
throws Exception {
boolean alice = random.nextBoolean();
TransportKeys transportKeys = createHandshakeKeys(1000, 0, alice);
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
// Get the current time (1 ms after start of time period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(timePeriodLength * 1000 + 1));
// Derive the transport keys
oneOf(transportCrypto).deriveHandshakeKeys(transportId, rootKey,
1000, alice);
will(returnValue(transportKeys));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
will(returnValue(keySetId));
}});
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
assertEquals(keySetId, transportKeyManager.addContactWithHandshakeKeys(
txn, contactId, rootKey, alice));
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
} }
@Test
public void testHandshakeKeysAreDerivedWhenAddingPendingContact()
throws Exception {
boolean alice = random.nextBoolean();
TransportKeys transportKeys = createHandshakeKeys(1000, 0, alice);
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
// Get the current time (1 ms after start of time period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(timePeriodLength * 1000 + 1));
// Derive the transport keys
oneOf(transportCrypto).deriveHandshakeKeys(transportId, rootKey,
1000, alice);
will(returnValue(transportKeys));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction());
}
// Save the keys
oneOf(db).addTransportKeys(txn, pendingContactId, transportKeys);
will(returnValue(keySetId));
}});
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
assertEquals(keySetId, transportKeyManager.addPendingContact(txn,
pendingContactId, rootKey, alice));
assertTrue(transportKeyManager.canSendOutgoingStreams(
pendingContactId));
}
@Test @Test
public void testOutgoingStreamContextIsNullIfContactIsNotFound() public void testOutgoingStreamContextIsNullIfContactIsNotFound()
throws Exception { throws Exception {
@@ -165,6 +245,19 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
} }
@Test
public void testOutgoingStreamContextIsNullIfPendingContactIsNotFound()
throws Exception {
Transaction txn = new Transaction(null, false);
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
maxLatency);
assertNull(transportKeyManager.getStreamContext(txn, pendingContactId));
assertFalse(transportKeyManager.canSendOutgoingStreams(
pendingContactId));
}
@Test @Test
public void testOutgoingStreamContextIsNullIfStreamCounterIsExhausted() public void testOutgoingStreamContextIsNullIfStreamCounterIsExhausted()
throws Exception { throws Exception {
@@ -181,8 +274,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, true)); txn, contactId, rootKey, timestamp, alice, true));
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
assertNull(transportKeyManager.getStreamContext(txn, contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId));
} }
@@ -207,8 +300,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, true)); txn, contactId, rootKey, timestamp, alice, true));
// The first request should return a stream context // The first request should return a stream context
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
StreamContext ctx = transportKeyManager.getStreamContext(txn, StreamContext ctx = transportKeyManager.getStreamContext(txn,
@@ -239,8 +332,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, active)); txn, contactId, rootKey, timestamp, alice, active));
assertEquals(active, assertEquals(active,
transportKeyManager.canSendOutgoingStreams(contactId)); transportKeyManager.canSendOutgoingStreams(contactId));
// The tag should not be recognised // The tag should not be recognised
@@ -292,8 +385,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, true)); txn, contactId, rootKey, timestamp, alice, true));
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
// Use the first tag (previous time period, stream number 0) // Use the first tag (previous time period, stream number 0)
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size()); assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
@@ -395,8 +488,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, false)); txn, contactId, rootKey, timestamp, alice, false));
// The keys are inactive so no stream context should be returned // The keys are inactive so no stream context should be returned
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
assertNull(transportKeyManager.getStreamContext(txn, contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId));
@@ -461,8 +554,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
maxLatency); maxLatency);
// The timestamp is at the start of time period 1000 // The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000; long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, assertEquals(keySetId, transportKeyManager.addContactWithRotationKeys(
rootKey, timestamp, alice, false)); txn, contactId, rootKey, timestamp, alice, false));
// The keys are inactive so no stream context should be returned // The keys are inactive so no stream context should be returned
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
assertNull(transportKeyManager.getStreamContext(txn, contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId));
@@ -525,6 +618,21 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
} }
@SuppressWarnings("SameParameterValue")
private TransportKeys createHandshakeKeys(long timePeriod,
long streamCounter, boolean alice) {
IncomingKeys inPrev = new IncomingKeys(tagKey, headerKey,
timePeriod - 1);
IncomingKeys inCurr = new IncomingKeys(tagKey, headerKey,
timePeriod);
IncomingKeys inNext = new IncomingKeys(tagKey, headerKey,
timePeriod + 1);
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
timePeriod, streamCounter, true);
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr,
rootKey, alice);
}
private class EncodeTagAction implements Action { private class EncodeTagAction implements Action {
private final Collection<byte[]> tags; private final Collection<byte[]> tags;

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactAddedRemotelyEvent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
@@ -32,7 +33,6 @@ import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent; import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
import org.briarproject.bramble.api.contact.event.ContactAddedRemotelyEvent;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Map; import java.util.Map;
@@ -445,7 +445,7 @@ class IntroduceeProtocolEngine
// add the keys to the new contact // add the keys to the new contact
//noinspection ConstantConditions //noinspection ConstantConditions
keys = keyManager.addContact(txn, c.getId(), keys = keyManager.addContactWithRotationKeys(txn, c.getId(),
new SecretKey(s.getMasterKey()), timestamp, new SecretKey(s.getMasterKey()), timestamp,
s.getLocal().alice, false); s.getLocal().alice, false);