mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 13:19:52 +01:00
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:
@@ -70,7 +70,8 @@ class ContactManagerImpl implements ContactManager {
|
||||
SecretKey rootKey, long timestamp, boolean alice, boolean verified,
|
||||
boolean active) throws DbException {
|
||||
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);
|
||||
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
|
||||
return c;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.transport;
|
||||
|
||||
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.crypto.SecretKey;
|
||||
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.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -88,14 +90,41 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, KeySetId> addContact(Transaction txn,
|
||||
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
|
||||
boolean active) throws DbException {
|
||||
public Map<TransportId, KeySetId> addContactWithRotationKeys(
|
||||
Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
|
||||
boolean alice, boolean active) 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.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;
|
||||
}
|
||||
@@ -104,13 +133,10 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
public void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
|
||||
throws DbException {
|
||||
for (Entry<TransportId, KeySetId> e : keys.entrySet()) {
|
||||
TransportId t = e.getKey();
|
||||
TransportKeyManager m = managers.get(t);
|
||||
if (m == null) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||
} else {
|
||||
withManager(e.getKey(), m -> {
|
||||
m.activateKeys(txn, e.getValue());
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,28 +146,34 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
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
|
||||
public StreamContext getStreamContext(ContactId c, TransportId t)
|
||||
throws DbException {
|
||||
TransportKeyManager m = managers.get(t);
|
||||
if (m == null) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||
return null;
|
||||
}
|
||||
return db.transactionWithNullableResult(false, txn ->
|
||||
m.getStreamContext(txn, c));
|
||||
return withManager(t, m ->
|
||||
db.transactionWithNullableResult(false, txn ->
|
||||
m.getStreamContext(txn, c)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamContext getStreamContext(PendingContactId p, TransportId t)
|
||||
throws DbException {
|
||||
return withManager(t, m ->
|
||||
db.transactionWithNullableResult(false, txn ->
|
||||
m.getStreamContext(txn, p)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamContext getStreamContext(TransportId t, byte[] tag)
|
||||
throws DbException {
|
||||
TransportKeyManager m = managers.get(t);
|
||||
if (m == null) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||
return null;
|
||||
}
|
||||
return db.transactionWithNullableResult(false, txn ->
|
||||
m.getStreamContext(txn, tag));
|
||||
return withManager(t, m ->
|
||||
db.transactionWithNullableResult(false, txn ->
|
||||
m.getStreamContext(txn, tag)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,4 +188,20 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireExactlyOneNull;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class MutableTransportKeySet {
|
||||
@@ -22,8 +24,7 @@ class MutableTransportKeySet {
|
||||
MutableTransportKeySet(KeySetId keySetId, @Nullable ContactId contactId,
|
||||
@Nullable PendingContactId pendingContactId,
|
||||
MutableTransportKeys keys) {
|
||||
if ((contactId == null) == (pendingContactId == null))
|
||||
throw new IllegalArgumentException();
|
||||
requireExactlyOneNull(contactId, pendingContactId);
|
||||
this.keySetId = keySetId;
|
||||
this.contactId = contactId;
|
||||
this.pendingContactId = pendingContactId;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.transport;
|
||||
|
||||
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.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
@@ -15,19 +16,34 @@ interface TransportKeyManager {
|
||||
|
||||
void start(Transaction txn) throws DbException;
|
||||
|
||||
KeySetId addContact(Transaction txn, ContactId c, SecretKey rootKey,
|
||||
long timestamp, boolean alice, boolean active) throws DbException;
|
||||
KeySetId addContactWithRotationKeys(Transaction txn, ContactId c,
|
||||
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 removeContact(ContactId c);
|
||||
|
||||
void removePendingContact(PendingContactId p);
|
||||
|
||||
boolean canSendOutgoingStreams(ContactId c);
|
||||
|
||||
boolean canSendOutgoingStreams(PendingContactId p);
|
||||
|
||||
@Nullable
|
||||
StreamContext getStreamContext(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
@Nullable
|
||||
StreamContext getStreamContext(Transaction txn, PendingContactId p)
|
||||
throws DbException;
|
||||
|
||||
@Nullable
|
||||
StreamContext getStreamContext(Transaction txn, byte[] tag)
|
||||
throws DbException;
|
||||
|
||||
@@ -36,6 +36,7 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
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.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
@@ -64,8 +65,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
@GuardedBy("lock")
|
||||
private final Map<Bytes, TagContext> inContexts = new HashMap<>();
|
||||
@GuardedBy("lock")
|
||||
private final Map<ContactId, MutableTransportKeySet> outContexts =
|
||||
new HashMap<>();
|
||||
private final Map<ContactId, MutableTransportKeySet>
|
||||
contactOutContexts = new HashMap<>();
|
||||
@GuardedBy("lock")
|
||||
private final Map<PendingContactId, MutableTransportKeySet>
|
||||
pendingContactOutContexts = new HashMap<>();
|
||||
|
||||
TransportKeyManagerImpl(DatabaseComponent db,
|
||||
TransportCrypto transportCrypto, Executor dbExecutor,
|
||||
@@ -133,6 +137,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
private void addKeys(KeySetId keySetId, @Nullable ContactId contactId,
|
||||
@Nullable PendingContactId pendingContactId,
|
||||
MutableTransportKeys keys) {
|
||||
requireExactlyOneNull(contactId, pendingContactId);
|
||||
MutableTransportKeySet ks = new MutableTransportKeySet(keySetId,
|
||||
contactId, pendingContactId, keys);
|
||||
this.keys.put(keySetId, ks);
|
||||
@@ -162,19 +167,30 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void considerReplacingOutgoingKeys(MutableTransportKeySet ks) {
|
||||
// Use the active outgoing keys with the highest key set ID
|
||||
ContactId c = ks.getContactId();
|
||||
if (c != null && ks.getKeys().getCurrentOutgoingKeys().isActive()) {
|
||||
MutableTransportKeySet old = outContexts.get(c);
|
||||
if (old == null ||
|
||||
(old.getKeys().isHandshakeMode() &&
|
||||
!ks.getKeys().isHandshakeMode()) ||
|
||||
// Use the active outgoing keys with the highest key set ID, preferring
|
||||
// rotation keys to handshake keys
|
||||
if (ks.getKeys().getCurrentOutgoingKeys().isActive()) {
|
||||
MutableTransportKeySet old = getOutgoingKeySet(ks.getContactId(),
|
||||
ks.getPendingContactId());
|
||||
if (old == null || (old.getKeys().isHandshakeMode() &&
|
||||
!ks.getKeys().isHandshakeMode()) ||
|
||||
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) {
|
||||
long delay = timePeriodLength - now % timePeriodLength;
|
||||
scheduler.schedule((Runnable) this::updateKeys, delay, MILLISECONDS);
|
||||
@@ -191,8 +207,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeySetId addContact(Transaction txn, ContactId c, SecretKey rootKey,
|
||||
long timestamp, boolean alice, boolean active) throws DbException {
|
||||
public KeySetId addContactWithRotationKeys(Transaction txn, ContactId c,
|
||||
SecretKey rootKey, long timestamp, boolean alice, boolean active)
|
||||
throws DbException {
|
||||
lock.lock();
|
||||
try {
|
||||
// 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
|
||||
public void activateKeys(Transaction txn, KeySetId k) throws DbException {
|
||||
lock.lock();
|
||||
@@ -234,8 +291,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
try {
|
||||
// Remove mutable state for the contact
|
||||
Iterator<TagContext> it = inContexts.values().iterator();
|
||||
while (it.hasNext()) if (c.equals(it.next().contactId)) it.remove();
|
||||
outContexts.remove(c);
|
||||
while (it.hasNext())
|
||||
if (c.equals(it.next().contactId)) it.remove();
|
||||
contactOutContexts.remove(c);
|
||||
Iterator<MutableTransportKeySet> it1 = keys.values().iterator();
|
||||
while (it1.hasNext())
|
||||
if (c.equals(it1.next().getContactId())) it1.remove();
|
||||
@@ -245,13 +303,39 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSendOutgoingStreams(ContactId c) {
|
||||
public void removePendingContact(PendingContactId p) {
|
||||
lock.lock();
|
||||
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;
|
||||
MutableOutgoingKeys outKeys =
|
||||
ks.getKeys().getCurrentOutgoingKeys();
|
||||
MutableOutgoingKeys outKeys = ks.getKeys().getCurrentOutgoingKeys();
|
||||
if (!outKeys.isActive()) throw new AssertionError();
|
||||
return outKeys.getStreamCounter() <= MAX_32_BIT_UNSIGNED;
|
||||
} finally {
|
||||
@@ -262,17 +346,30 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
@Override
|
||||
public StreamContext getStreamContext(Transaction txn, ContactId c)
|
||||
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();
|
||||
try {
|
||||
// Look up the outgoing keys for the contact
|
||||
MutableTransportKeySet ks = outContexts.get(c);
|
||||
MutableTransportKeySet ks = getOutgoingKeySet(c, p);
|
||||
if (ks == null) return null;
|
||||
MutableTransportKeys keys = ks.getKeys();
|
||||
MutableOutgoingKeys outKeys = keys.getCurrentOutgoingKeys();
|
||||
if (!outKeys.isActive()) throw new AssertionError();
|
||||
if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null;
|
||||
// Create a stream context
|
||||
StreamContext ctx = new StreamContext(c, null, transportId,
|
||||
StreamContext ctx = new StreamContext(c, p, transportId,
|
||||
outKeys.getTagKey(), outKeys.getHeaderKey(),
|
||||
outKeys.getStreamCounter(), keys.isHandshakeMode());
|
||||
// Increment the stream counter and write it back to the DB
|
||||
@@ -353,7 +450,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
UpdateResult updateResult = updateKeys(snapshot, now);
|
||||
// Rebuild the mutable state for all contacts
|
||||
inContexts.clear();
|
||||
outContexts.clear();
|
||||
contactOutContexts.clear();
|
||||
pendingContactOutContexts.clear();
|
||||
keys.clear();
|
||||
addKeys(updateResult.current);
|
||||
// Write any updated keys back to the DB
|
||||
@@ -381,6 +479,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
@Nullable PendingContactId pendingContactId,
|
||||
MutableIncomingKeys inKeys, long streamNumber,
|
||||
boolean handshakeMode) {
|
||||
requireExactlyOneNull(contactId, pendingContactId);
|
||||
this.keySetId = keySetId;
|
||||
this.contactId = contactId;
|
||||
this.pendingContactId = pendingContactId;
|
||||
|
||||
Reference in New Issue
Block a user