diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java index d54bff1c3..c72b0fa7e 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java @@ -18,30 +18,30 @@ public interface ContactExchangeTask { byte PROTOCOL_VERSION = 1; /** - * Label for deriving Alice's header key from the master secret. + * Label for deriving Alice's header key from the master key. */ String ALICE_KEY_LABEL = "org.briarproject.bramble.contact/ALICE_HEADER_KEY"; /** - * Label for deriving Bob's header key from the master secret. + * Label for deriving Bob's header key from the master key. */ String BOB_KEY_LABEL = "org.briarproject.bramble.contact/BOB_HEADER_KEY"; /** - * Label for deriving Alice's key binding nonce from the master secret. + * Label for deriving Alice's key binding nonce from the master key. */ String ALICE_NONCE_LABEL = "org.briarproject.bramble.contact/ALICE_NONCE"; /** - * Label for deriving Bob's key binding nonce from the master secret. + * Label for deriving Bob's key binding nonce from the master key. */ String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE"; /** * Exchanges contact information with a remote peer. */ - void startExchange(LocalAuthor localAuthor, SecretKey masterSecret, + void startExchange(LocalAuthor localAuthor, SecretKey masterKey, DuplexTransportConnection conn, TransportId transportId, boolean alice); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java index 7996cbf54..ce07140fa 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java @@ -13,8 +13,6 @@ import java.util.Collection; import javax.annotation.Nullable; -import static org.briarproject.bramble.api.contact.PendingContact.PendingContactState.FAILED; - @NotNullByDefault public interface ContactManager { @@ -33,7 +31,7 @@ public interface ContactManager { * @param alice true if the local party is Alice */ ContactId addContact(Transaction txn, Author remote, AuthorId local, - SecretKey master, long timestamp, boolean alice, boolean verified, + SecretKey rootKey, long timestamp, boolean alice, boolean verified, boolean active) throws DbException; /** @@ -50,7 +48,7 @@ public interface ContactManager { * * @param alice true if the local party is Alice */ - ContactId addContact(Author remote, AuthorId local, SecretKey master, + ContactId addContact(Author remote, AuthorId local, SecretKey rootKey, long timestamp, boolean alice, boolean verified, boolean active) throws DbException; @@ -79,7 +77,8 @@ public interface ContactManager { Collection getPendingContacts(); /** - * Removes a {@link PendingContact} that is in state {@link FAILED}. + * Removes a {@link PendingContact} that is in state + * {@link PendingContactState FAILED}. */ void removePendingContact(PendingContact pendingContact); diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContact.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContact.java index 025803026..4989a11cf 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContact.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContact.java @@ -8,26 +8,29 @@ import javax.annotation.concurrent.Immutable; @NotNullByDefault public class PendingContact { - public enum PendingContactState { - WAITING_FOR_CONNECTION, - CONNECTED, - ADDING_CONTACT, - FAILED - } - private final PendingContactId id; + private final byte[] publicKey; private final String alias; private final PendingContactState state; private final long timestamp; - public PendingContact(PendingContactId id, String alias, - PendingContactState state, long timestamp) { + public PendingContact(PendingContactId id, byte[] publicKey, + String alias, PendingContactState state, long timestamp) { this.id = id; + this.publicKey = publicKey; this.alias = alias; this.state = state; this.timestamp = timestamp; } + public PendingContactId getId() { + return id; + } + + public byte[] getPublicKey() { + return publicKey; + } + public String getAlias() { return alias; } @@ -50,5 +53,4 @@ public class PendingContact { return o instanceof PendingContact && id.equals(((PendingContact) o).id); } - } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContactState.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContactState.java new file mode 100644 index 000000000..5fd5a9919 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/PendingContactState.java @@ -0,0 +1,30 @@ +package org.briarproject.bramble.api.contact; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public enum PendingContactState { + + WAITING_FOR_CONNECTION(0), + CONNECTED(1), + ADDING_CONTACT(2), + FAILED(3); + + private final int value; + + PendingContactState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static PendingContactState fromValue(int value) { + for (PendingContactState s : values()) if (s.value == value) return s; + throw new IllegalArgumentException(); + } +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/event/PendingContactStateChangedEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/event/PendingContactStateChangedEvent.java index b5a8dc11a..6e7b454c3 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/event/PendingContactStateChangedEvent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/event/PendingContactStateChangedEvent.java @@ -1,7 +1,7 @@ package org.briarproject.bramble.api.contact.event; -import org.briarproject.bramble.api.contact.PendingContact.PendingContactState; import org.briarproject.bramble.api.contact.PendingContactId; +import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/TransportCrypto.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/TransportCrypto.java index 666abffc6..a77f46157 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/TransportCrypto.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/TransportCrypto.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.api.crypto; import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.transport.HandshakeKeys; import org.briarproject.bramble.api.transport.TransportKeys; /** @@ -11,19 +12,34 @@ public interface TransportCrypto { /** * Derives initial transport keys for the given transport in the given - * rotation period from the given master secret. + * time period from the given root key. * * @param alice whether the keys are for use by Alice or Bob. * @param active whether the keys are usable for outgoing streams. */ - TransportKeys deriveTransportKeys(TransportId t, SecretKey master, - long rotationPeriod, boolean alice, boolean active); + TransportKeys deriveTransportKeys(TransportId t, SecretKey rootKey, + long timePeriod, boolean alice, boolean active); /** - * Rotates the given transport keys to the given rotation period. If the - * keys are for the given period or any later period they are not rotated. + * Rotates the given transport keys to the given time period. If the keys + * are for the given period or any later period they are not rotated. */ - TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod); + TransportKeys rotateTransportKeys(TransportKeys k, long timePeriod); + + /** + * Derives handshake keys for the given transport in the given time period + * from the given root key. + * + * @param alice whether the keys are for use by Alice or Bob. + */ + HandshakeKeys deriveHandshakeKeys(TransportId t, SecretKey rootKey, + long timePeriod, boolean alice); + + /** + * Updates the given handshake keys to the given time period. If the keys + * are for the given period or any later period they are not updated. + */ + HandshakeKeys updateHandshakeKeys(HandshakeKeys k, long timePeriod); /** * Encodes the pseudo-random tag that is used to recognise a stream. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java index 9ec8b5c7b..563ce2d8e 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java @@ -2,6 +2,8 @@ package org.briarproject.bramble.api.db; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorId; @@ -20,8 +22,11 @@ import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.validation.MessageState; -import org.briarproject.bramble.api.transport.KeySet; -import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.transport.HandshakeKeySet; +import org.briarproject.bramble.api.transport.HandshakeKeySetId; +import org.briarproject.bramble.api.transport.HandshakeKeys; +import org.briarproject.bramble.api.transport.TransportKeySet; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.api.transport.TransportKeys; import java.util.Collection; @@ -108,6 +113,20 @@ public interface DatabaseComponent { */ void addGroup(Transaction txn, Group g) throws DbException; + /** + * Stores the given handshake keys for the given contact and returns a + * key set ID. + */ + HandshakeKeySetId addHandshakeKeys(Transaction txn, ContactId c, + HandshakeKeys k) throws DbException; + + /** + * Stores the given handshake keys for the given pending contact and + * returns a key set ID. + */ + HandshakeKeySetId addHandshakeKeys(Transaction txn, PendingContactId p, + HandshakeKeys k) throws DbException; + /** * Stores a local pseudonym. */ @@ -119,6 +138,12 @@ public interface DatabaseComponent { void addLocalMessage(Transaction txn, Message m, Metadata meta, boolean shared) throws DbException; + /** + * Stores a pending contact. + */ + void addPendingContact(Transaction txn, PendingContact p) + throws DbException; + /** * Stores a transport. */ @@ -129,27 +154,41 @@ public interface DatabaseComponent { * Stores the given transport keys for the given contact and returns a * key set ID. */ - KeySetId addTransportKeys(Transaction txn, ContactId c, + TransportKeySetId addTransportKeys(Transaction txn, ContactId c, TransportKeys k) throws DbException; /** * Returns true if the database contains the given contact for the given * local pseudonym. + *

+ * Read-only. */ boolean containsContact(Transaction txn, AuthorId remote, AuthorId local) throws DbException; /** * Returns true if the database contains the given group. + *

+ * Read-only. */ boolean containsGroup(Transaction txn, GroupId g) throws DbException; /** * Returns true if the database contains the given local author. + *

+ * Read-only. */ boolean containsLocalAuthor(Transaction txn, AuthorId local) throws DbException; + /** + * Returns true if the database contains the given pending contact. + *

+ * Read-only. + */ + boolean containsPendingContact(Transaction txn, PendingContactId p) + throws DbException; + /** * Deletes the message with the given ID. Unlike * {@link #removeMessage(Transaction, MessageId)}, the message ID, @@ -269,6 +308,14 @@ public interface DatabaseComponent { Visibility getGroupVisibility(Transaction txn, ContactId c, GroupId g) throws DbException; + /** + * Returns all handshake keys for the given transport. + *

+ * Read-only. + */ + Collection getHandshakeKeys(Transaction txn, TransportId t) + throws DbException; + /** * Returns the local pseudonym with the given ID. *

@@ -417,6 +464,14 @@ public interface DatabaseComponent { */ long getNextSendTime(Transaction txn, ContactId c) throws DbException; + /** + * Returns all pending contacts. + *

+ * Read-only. + */ + Collection getPendingContacts(Transaction txn) + throws DbException; + /** * Returns all settings in the given namespace. *

@@ -429,14 +484,20 @@ public interface DatabaseComponent { *

* Read-only. */ - Collection getTransportKeys(Transaction txn, TransportId t) + Collection getTransportKeys(Transaction txn, TransportId t) throws DbException; + /** + * Increments the outgoing stream counter for the given handshake keys. + */ + void incrementStreamCounter(Transaction txn, TransportId t, + HandshakeKeySetId k) throws DbException; + /** * Increments the outgoing stream counter for the given transport keys. */ - void incrementStreamCounter(Transaction txn, TransportId t, KeySetId k) - throws DbException; + void incrementStreamCounter(Transaction txn, TransportId t, + TransportKeySetId k) throws DbException; /** * Merges the given metadata with the existing metadata for the given @@ -491,6 +552,12 @@ public interface DatabaseComponent { */ void removeGroup(Transaction txn, Group g) throws DbException; + /** + * Removes the given handshake keys from the database. + */ + void removeHandshakeKeys(Transaction txn, TransportId t, + HandshakeKeySetId k) throws DbException; + /** * Removes a local pseudonym (and all associated state) from the database. */ @@ -501,6 +568,12 @@ public interface DatabaseComponent { */ void removeMessage(Transaction txn, MessageId m) throws DbException; + /** + * Removes a pending contact (and all associated state) from the database. + */ + void removePendingContact(Transaction txn, PendingContactId p) + throws DbException; + /** * Removes a transport (and all associated state) from the database. */ @@ -509,8 +582,8 @@ public interface DatabaseComponent { /** * Removes the given transport keys from the database. */ - void removeTransportKeys(Transaction txn, TransportId t, KeySetId k) - throws DbException; + void removeTransportKeys(Transaction txn, TransportId t, + TransportKeySetId k) throws DbException; /** * Marks the given contact as verified. @@ -553,21 +626,36 @@ public interface DatabaseComponent { Collection dependencies) throws DbException; /** - * Sets the reordering window for the given key set and transport in the - * given rotation period. + * Sets the reordering window for the given transport key set in the given + * time period. */ - void setReorderingWindow(Transaction txn, KeySetId k, TransportId t, - long rotationPeriod, long base, byte[] bitmap) throws DbException; + void setReorderingWindow(Transaction txn, TransportKeySetId k, + TransportId t, long timePeriod, long base, byte[] bitmap) + throws DbException; + + /** + * Sets the reordering window for the given handshake key set in the given + * time period. + */ + void setReorderingWindow(Transaction txn, HandshakeKeySetId k, + TransportId t, long timePeriod, long base, byte[] bitmap) + throws DbException; /** * Marks the given transport keys as usable for outgoing streams. */ - void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k) + void setTransportKeysActive(Transaction txn, TransportId t, + TransportKeySetId k) throws DbException; + + /** + * Stores the given handshake keys, deleting any keys they have replaced. + */ + void updateHandshakeKeys(Transaction txn, Collection keys) throws DbException; /** * Stores the given transport keys, deleting any keys they have replaced. */ - void updateTransportKeys(Transaction txn, Collection keys) + void updateTransportKeys(Transaction txn, Collection keys) throws DbException; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/NoSuchPendingContactException.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/NoSuchPendingContactException.java new file mode 100644 index 000000000..d620e6d36 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/NoSuchPendingContactException.java @@ -0,0 +1,9 @@ +package org.briarproject.bramble.api.db; + +/** + * Thrown when a database operation is attempted for a pending contact that is + * not in the database. This exception may occur due to concurrent updates and + * does not indicate a database error. + */ +public class NoSuchPendingContactException extends DbException { +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/PendingContactExistsException.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/PendingContactExistsException.java new file mode 100644 index 000000000..cefca3f0a --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/PendingContactExistsException.java @@ -0,0 +1,9 @@ +package org.briarproject.bramble.api.db; + +/** + * Thrown when a duplicate pending contact is added to the database. This + * exception may occur due to concurrent updates and does not indicate a + * database error. + */ +public class PendingContactExistsException extends DbException { +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java index 4041d0ce4..2f5170b91 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java @@ -40,8 +40,8 @@ public interface KeyAgreementConstants { "org.briarproject.bramble.keyagreement/SHARED_SECRET"; /** - * Label for deriving the master secret. + * Label for deriving the master key. */ - String MASTER_SECRET_LABEL = + String MASTER_KEY_LABEL = "org.briarproject.bramble.keyagreement/MASTER_SECRET"; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/AbstractTransportKeys.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/AbstractTransportKeys.java new file mode 100644 index 000000000..d054983d2 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/AbstractTransportKeys.java @@ -0,0 +1,57 @@ +package org.briarproject.bramble.api.transport; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; + +import javax.annotation.concurrent.Immutable; + +/** + * Abstract superclass for {@link TransportKeys} and {@link HandshakeKeys}. + */ +@Immutable +@NotNullByDefault +public abstract class AbstractTransportKeys { + + private final TransportId transportId; + private final IncomingKeys inPrev, inCurr, inNext; + private final OutgoingKeys outCurr; + + AbstractTransportKeys(TransportId transportId, IncomingKeys inPrev, + IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr) { + if (inPrev.getTimePeriod() != outCurr.getTimePeriod() - 1) + throw new IllegalArgumentException(); + if (inCurr.getTimePeriod() != outCurr.getTimePeriod()) + throw new IllegalArgumentException(); + if (inNext.getTimePeriod() != outCurr.getTimePeriod() + 1) + throw new IllegalArgumentException(); + this.transportId = transportId; + this.inPrev = inPrev; + this.inCurr = inCurr; + this.inNext = inNext; + this.outCurr = outCurr; + } + + public TransportId getTransportId() { + return transportId; + } + + public IncomingKeys getPreviousIncomingKeys() { + return inPrev; + } + + public IncomingKeys getCurrentIncomingKeys() { + return inCurr; + } + + public IncomingKeys getNextIncomingKeys() { + return inNext; + } + + public OutgoingKeys getCurrentOutgoingKeys() { + return outCurr; + } + + public long getTimePeriod() { + return outCurr.getTimePeriod(); + } +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/HandshakeKeySet.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/HandshakeKeySet.java new file mode 100644 index 000000000..cb40da1da --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/HandshakeKeySet.java @@ -0,0 +1,70 @@ +package org.briarproject.bramble.api.transport; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.PendingContactId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * A set of keys for handshaking with a given contact or pending contact over a + * given transport. Unlike a {@link TransportKeySet} these keys do not provide + * forward secrecy. + */ +@Immutable +@NotNullByDefault +public class HandshakeKeySet { + + private final HandshakeKeySetId keySetId; + @Nullable + private final ContactId contactId; + @Nullable + private final PendingContactId pendingContactId; + private final HandshakeKeys keys; + + public HandshakeKeySet(HandshakeKeySetId keySetId, ContactId contactId, + HandshakeKeys keys) { + this.keySetId = keySetId; + this.contactId = contactId; + this.keys = keys; + pendingContactId = null; + } + + public HandshakeKeySet(HandshakeKeySetId keySetId, + PendingContactId pendingContactId, HandshakeKeys keys) { + this.keySetId = keySetId; + this.pendingContactId = pendingContactId; + this.keys = keys; + contactId = null; + } + + public HandshakeKeySetId getKeySetId() { + return keySetId; + } + + @Nullable + public ContactId getContactId() { + return contactId; + } + + @Nullable + public PendingContactId getPendingContactId() { + return pendingContactId; + } + + public HandshakeKeys getKeys() { + return keys; + } + + @Override + public int hashCode() { + return keySetId.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof HandshakeKeySet && + keySetId.equals(((HandshakeKeySet) o).keySetId); + } +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/HandshakeKeySetId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/HandshakeKeySetId.java new file mode 100644 index 000000000..f54c3bb7a --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/HandshakeKeySetId.java @@ -0,0 +1,36 @@ +package org.briarproject.bramble.api.transport; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +/** + * Type-safe wrapper for an integer that uniquely identifies a + * {@link HandshakeKeySet set of handshake keys} within the scope of the local + * device. + */ +@Immutable +@NotNullByDefault +public class HandshakeKeySetId { + + private final int id; + + public HandshakeKeySetId(int id) { + this.id = id; + } + + public int getInt() { + return id; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object o) { + return o instanceof HandshakeKeySetId && + id == ((HandshakeKeySetId) o).id; + } +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/HandshakeKeys.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/HandshakeKeys.java new file mode 100644 index 000000000..4a27e9adc --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/HandshakeKeys.java @@ -0,0 +1,36 @@ +package org.briarproject.bramble.api.transport; + +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; + +import javax.annotation.concurrent.Immutable; + +/** + * Keys for handshaking with a given contact or pending contact over a given + * transport. Unlike {@link TransportKeys} these keys do not provide forward + * secrecy. + */ +@Immutable +@NotNullByDefault +public class HandshakeKeys extends AbstractTransportKeys { + + private final SecretKey rootKey; + private final boolean alice; + + public HandshakeKeys(TransportId transportId, IncomingKeys inPrev, + IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr, + SecretKey rootKey, boolean alice) { + super(transportId, inPrev, inCurr, inNext, outCurr); + this.rootKey = rootKey; + this.alice = alice; + } + + public SecretKey getRootKey() { + return rootKey; + } + + public boolean isAlice() { + return alice; + } +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/IncomingKeys.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/IncomingKeys.java index 1b7942512..46a17887e 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/IncomingKeys.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/IncomingKeys.java @@ -9,27 +9,27 @@ import static org.briarproject.bramble.api.transport.TransportConstants.REORDERI /** * Contains transport keys for receiving streams from a given contact over a - * given transport in a given rotation period. + * given transport in a given time period. */ @Immutable @NotNullByDefault public class IncomingKeys { private final SecretKey tagKey, headerKey; - private final long rotationPeriod, windowBase; + private final long timePeriod, windowBase; private final byte[] windowBitmap; public IncomingKeys(SecretKey tagKey, SecretKey headerKey, - long rotationPeriod) { - this(tagKey, headerKey, rotationPeriod, 0, + long timePeriod) { + this(tagKey, headerKey, timePeriod, 0, new byte[REORDERING_WINDOW_SIZE / 8]); } public IncomingKeys(SecretKey tagKey, SecretKey headerKey, - long rotationPeriod, long windowBase, byte[] windowBitmap) { + long timePeriod, long windowBase, byte[] windowBitmap) { this.tagKey = tagKey; this.headerKey = headerKey; - this.rotationPeriod = rotationPeriod; + this.timePeriod = timePeriod; this.windowBase = windowBase; this.windowBitmap = windowBitmap; } @@ -42,8 +42,8 @@ public class IncomingKeys { return headerKey; } - public long getRotationPeriod() { - return rotationPeriod; + public long getTimePeriod() { + return timePeriod; } public long getWindowBase() { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java index 7071a5e47..e5c1ac541 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java @@ -27,14 +27,14 @@ public interface KeyManager { * @param alice true if the local party is Alice * @param active whether the derived keys can be used for outgoing streams */ - Map addContact(Transaction txn, ContactId c, - SecretKey master, long timestamp, boolean alice, boolean active) + Map addContact(Transaction txn, ContactId c, + SecretKey rootKey, long timestamp, boolean alice, boolean active) throws DbException; /** * Marks the given transport keys as usable for outgoing streams. */ - void activateKeys(Transaction txn, Map keys) + void activateKeys(Transaction txn, Map keys) throws DbException; /** diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeySet.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeySet.java deleted file mode 100644 index 47fc65288..000000000 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeySet.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.briarproject.bramble.api.transport; - -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import javax.annotation.concurrent.Immutable; - -/** - * A set of transport keys for communicating with a contact. - */ -@Immutable -@NotNullByDefault -public class KeySet { - - private final KeySetId keySetId; - private final ContactId contactId; - private final TransportKeys transportKeys; - - public KeySet(KeySetId keySetId, ContactId contactId, - TransportKeys transportKeys) { - this.keySetId = keySetId; - this.contactId = contactId; - this.transportKeys = transportKeys; - } - - public KeySetId getKeySetId() { - return keySetId; - } - - public ContactId getContactId() { - return contactId; - } - - public TransportKeys getTransportKeys() { - return transportKeys; - } - - @Override - public int hashCode() { - return keySetId.hashCode(); - } - - @Override - public boolean equals(Object o) { - return o instanceof KeySet && keySetId.equals(((KeySet) o).keySetId); - } -} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java index 2cdbfadaf..5e06e9b40 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/OutgoingKeys.java @@ -7,26 +7,26 @@ import javax.annotation.concurrent.Immutable; /** * Contains transport keys for sending streams to a given contact over a given - * transport in a given rotation period. + * transport in a given time period. */ @Immutable @NotNullByDefault public class OutgoingKeys { private final SecretKey tagKey, headerKey; - private final long rotationPeriod, streamCounter; + private final long timePeriod, streamCounter; private final boolean active; public OutgoingKeys(SecretKey tagKey, SecretKey headerKey, - long rotationPeriod, boolean active) { - this(tagKey, headerKey, rotationPeriod, 0, active); + long timePeriod, boolean active) { + this(tagKey, headerKey, timePeriod, 0, active); } public OutgoingKeys(SecretKey tagKey, SecretKey headerKey, - long rotationPeriod, long streamCounter, boolean active) { + long timePeriod, long streamCounter, boolean active) { this.tagKey = tagKey; this.headerKey = headerKey; - this.rotationPeriod = rotationPeriod; + this.timePeriod = timePeriod; this.streamCounter = streamCounter; this.active = active; } @@ -39,8 +39,8 @@ public class OutgoingKeys { return headerKey; } - public long getRotationPeriod() { - return rotationPeriod; + public long getTimePeriod() { + return timePeriod; } public long getStreamCounter() { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportConstants.java index cf5502a5f..ce2394d74 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportConstants.java @@ -82,30 +82,58 @@ public interface TransportConstants { int REORDERING_WINDOW_SIZE = 32; /** - * Label for deriving Alice's initial tag key from the master secret. + * Label for deriving Alice's initial tag key from the root key in + * rotation mode. */ String ALICE_TAG_LABEL = "org.briarproject.bramble.transport/ALICE_TAG_KEY"; /** - * Label for deriving Bob's initial tag key from the master secret. + * Label for deriving Bob's initial tag key from the root key in rotation + * mode. */ String BOB_TAG_LABEL = "org.briarproject.bramble.transport/BOB_TAG_KEY"; /** - * Label for deriving Alice's initial header key from the master secret. + * Label for deriving Alice's initial header key from the root key in + * rotation mode. */ String ALICE_HEADER_LABEL = "org.briarproject.bramble.transport/ALICE_HEADER_KEY"; /** - * Label for deriving Bob's initial header key from the master secret. + * Label for deriving Bob's initial header key from the root key in + * rotation mode. */ String BOB_HEADER_LABEL = "org.briarproject.bramble.transport/BOB_HEADER_KEY"; /** - * Label for deriving the next period's key in key rotation. + * Label for deriving the next period's key in rotation mode. */ String ROTATE_LABEL = "org.briarproject.bramble.transport/ROTATE"; + /** + * Label for deriving Alice's tag key from the root key in handshake mode. + */ + String ALICE_HANDSHAKE_TAG_LABEL = + "org.briarproject.bramble.transport/ALICE_HANDSHAKE_TAG_KEY"; + + /** + * Label for deriving Bob's tag key from the root key in handshake mode. + */ + String BOB_HANDSHAKE_TAG_LABEL = + "org.briarproject.bramble.transport/BOB_HANDSHAKE_TAG_KEY"; + + /** + * Label for deriving Alice's header key from the root key in handshake + * mode. + */ + String ALICE_HANDSHAKE_HEADER_LABEL = + "org.briarproject.bramble.transport/ALICE_HANDSHAKE_HEADER_KEY"; + + /** + * Label for deriving Bob's header key from the root key in handshake mode. + */ + String BOB_HANDSHAKE_HEADER_LABEL = + "org.briarproject.bramble.transport/BOB_HANDSHAKE_HEADER_KEY"; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeySet.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeySet.java new file mode 100644 index 000000000..4b3831bc3 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeySet.java @@ -0,0 +1,49 @@ +package org.briarproject.bramble.api.transport; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +/** + * A set of keys for communicating with a given contact over a given transport. + * Unlike a {@link HandshakeKeySet} these keys provide forward secrecy. + */ +@Immutable +@NotNullByDefault +public class TransportKeySet { + + private final TransportKeySetId keySetId; + private final ContactId contactId; + private final TransportKeys keys; + + public TransportKeySet(TransportKeySetId keySetId, ContactId contactId, + TransportKeys keys) { + this.keySetId = keySetId; + this.contactId = contactId; + this.keys = keys; + } + + public TransportKeySetId getKeySetId() { + return keySetId; + } + + public ContactId getContactId() { + return contactId; + } + + public TransportKeys getKeys() { + return keys; + } + + @Override + public int hashCode() { + return keySetId.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof TransportKeySet && + keySetId.equals(((TransportKeySet) o).keySetId); + } +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeySetId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeySetId.java similarity index 60% rename from bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeySetId.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeySetId.java index 1f872e72a..c4b1ae088 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeySetId.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeySetId.java @@ -5,18 +5,19 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import javax.annotation.concurrent.Immutable; /** - * Type-safe wrapper for an integer that uniquely identifies a set of transport - * keys within the scope of the local device. + * Type-safe wrapper for an integer that uniquely identifies a + * {@link TransportKeySet set of transport keys} within the scope of the local + * device. *

* Key sets created on a given device must have increasing identifiers. */ @Immutable @NotNullByDefault -public class KeySetId { +public class TransportKeySetId { private final int id; - public KeySetId(int id) { + public TransportKeySetId(int id) { this.id = id; } @@ -31,6 +32,7 @@ public class KeySetId { @Override public boolean equals(Object o) { - return o instanceof KeySetId && id == ((KeySetId) o).id; + return o instanceof TransportKeySetId && + id == ((TransportKeySetId) o).id; } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeys.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeys.java index 58795445b..39a27d45b 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeys.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportKeys.java @@ -6,52 +6,15 @@ import org.briarproject.bramble.api.plugin.TransportId; import javax.annotation.concurrent.Immutable; /** - * Keys for communicating with a given contact over a given transport. + * Keys for communicating with a given contact over a given transport. Unlike + * {@link HandshakeKeys} these keys provide forward secrecy. */ @Immutable @NotNullByDefault -public class TransportKeys { - - private final TransportId transportId; - private final IncomingKeys inPrev, inCurr, inNext; - private final OutgoingKeys outCurr; +public class TransportKeys extends AbstractTransportKeys { public TransportKeys(TransportId transportId, IncomingKeys inPrev, IncomingKeys inCurr, IncomingKeys inNext, OutgoingKeys outCurr) { - if (inPrev.getRotationPeriod() != outCurr.getRotationPeriod() - 1) - throw new IllegalArgumentException(); - if (inCurr.getRotationPeriod() != outCurr.getRotationPeriod()) - throw new IllegalArgumentException(); - if (inNext.getRotationPeriod() != outCurr.getRotationPeriod() + 1) - throw new IllegalArgumentException(); - this.transportId = transportId; - this.inPrev = inPrev; - this.inCurr = inCurr; - this.inNext = inNext; - this.outCurr = outCurr; - } - - public TransportId getTransportId() { - return transportId; - } - - public IncomingKeys getPreviousIncomingKeys() { - return inPrev; - } - - public IncomingKeys getCurrentIncomingKeys() { - return inCurr; - } - - public IncomingKeys getNextIncomingKeys() { - return inNext; - } - - public OutgoingKeys getCurrentOutgoingKeys() { - return outCurr; - } - - public long getRotationPeriod() { - return outCurr.getRotationPeriod(); + super(transportId, inPrev, inCurr, inNext, outCurr); } } diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java index d33304dd0..550fefbb5 100644 --- a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java +++ b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java @@ -1,6 +1,8 @@ package org.briarproject.bramble.test; import org.briarproject.bramble.api.UniqueId; +import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorId; @@ -25,6 +27,7 @@ import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import static java.util.Arrays.asList; +import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; @@ -140,6 +143,18 @@ public class TestUtils { return new Message(id, groupId, timestamp, body); } + public static PendingContact getPendingContact() { + return getPendingContact(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH)); + } + + public static PendingContact getPendingContact(int nameLength) { + PendingContactId id = new PendingContactId(getRandomId()); + byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + String alias = getRandomString(nameLength); + return new PendingContact(id, publicKey, alias, WAITING_FOR_CONNECTION, + timestamp); + } + public static double getMedian(Collection samples) { int size = samples.size(); if (size == 0) throw new IllegalArgumentException(); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java index 76163e696..061a43a48 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java @@ -77,7 +77,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { private volatile LocalAuthor localAuthor; private volatile DuplexTransportConnection conn; private volatile TransportId transportId; - private volatile SecretKey masterSecret; + private volatile SecretKey masterKey; private volatile boolean alice; @Inject @@ -104,13 +104,13 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { } @Override - public void startExchange(LocalAuthor localAuthor, SecretKey masterSecret, + public void startExchange(LocalAuthor localAuthor, SecretKey masterKey, DuplexTransportConnection conn, TransportId transportId, boolean alice) { this.localAuthor = localAuthor; this.conn = conn; this.transportId = transportId; - this.masterSecret = masterSecret; + this.masterKey = masterKey; this.alice = alice; start(); } @@ -142,9 +142,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { } // Derive the header keys for the transport streams - SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL, - masterSecret, new byte[] {PROTOCOL_VERSION}); - SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret, + SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL, masterKey, + new byte[] {PROTOCOL_VERSION}); + SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterKey, new byte[] {PROTOCOL_VERSION}); // Create the readers @@ -163,9 +163,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { .createRecordWriter(streamWriter.getOutputStream()); // Derive the nonces to be signed - byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret, + byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterKey, new byte[] {PROTOCOL_VERSION}); - byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret, + byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterKey, new byte[] {PROTOCOL_VERSION}); byte[] localNonce = alice ? aliceNonce : bobNonce; byte[] remoteNonce = alice ? bobNonce : aliceNonce; @@ -293,7 +293,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { throws DbException { return db.transactionWithResult(false, txn -> { ContactId contactId = contactManager.addContact(txn, remoteAuthor, - localAuthor.getId(), masterSecret, timestamp, alice, + localAuthor.getId(), masterKey, timestamp, alice, true, true); transportPropertyManager.addRemoteProperties(txn, contactId, remoteProperties); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java index bee28f7d2..a0f0c4351 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java @@ -30,8 +30,9 @@ import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; import static java.util.Collections.emptyList; -import static org.briarproject.bramble.api.contact.PendingContact.PendingContactState.WAITING_FOR_CONNECTION; +import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED; @@ -69,10 +70,10 @@ class ContactManagerImpl implements ContactManager { @Override public ContactId addContact(Transaction txn, Author remote, AuthorId local, - SecretKey master, long timestamp, boolean alice, boolean verified, + SecretKey rootKey, long timestamp, boolean alice, boolean verified, boolean active) throws DbException { ContactId c = db.addContact(txn, remote, local, verified, active); - keyManager.addContact(txn, c, master, timestamp, alice, active); + keyManager.addContact(txn, c, rootKey, timestamp, alice, active); Contact contact = db.getContact(txn, c); for (ContactHook hook : hooks) hook.addingContact(txn, contact); return c; @@ -88,11 +89,11 @@ class ContactManagerImpl implements ContactManager { } @Override - public ContactId addContact(Author remote, AuthorId local, SecretKey master, - long timestamp, boolean alice, boolean verified, boolean active) - throws DbException { + public ContactId addContact(Author remote, AuthorId local, + SecretKey rootKey, long timestamp, boolean alice, boolean verified, + boolean active) throws DbException { return db.transactionWithResult(false, txn -> - addContact(txn, remote, local, master, timestamp, alice, + addContact(txn, remote, local, rootKey, timestamp, alice, verified, active)); } @@ -123,8 +124,8 @@ class ContactManagerImpl implements ContactManager { public PendingContact addRemoteContactRequest(String link, String alias) { // TODO replace with real implementation PendingContactId id = new PendingContactId(link.getBytes()); - return new PendingContact(id, alias, WAITING_FOR_CONNECTION, - System.currentTimeMillis()); + return new PendingContact(id, new byte[MAX_PUBLIC_KEY_LENGTH], alias, + WAITING_FOR_CONNECTION, System.currentTimeMillis()); } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/TransportCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/TransportCryptoImpl.java index 2d4ffb7d3..5ee6b87ee 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/TransportCryptoImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/TransportCryptoImpl.java @@ -4,18 +4,22 @@ import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.TransportCrypto; import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.transport.HandshakeKeys; import org.briarproject.bramble.api.transport.IncomingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.TransportKeys; -import org.briarproject.bramble.util.ByteUtils; -import org.briarproject.bramble.util.StringUtils; import org.spongycastle.crypto.Digest; import org.spongycastle.crypto.digests.Blake2bDigest; import javax.inject.Inject; +import static java.lang.System.arraycopy; +import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_HANDSHAKE_HEADER_LABEL; +import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_HANDSHAKE_TAG_LABEL; import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_HEADER_LABEL; import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_TAG_LABEL; +import static org.briarproject.bramble.api.transport.TransportConstants.BOB_HANDSHAKE_HEADER_LABEL; +import static org.briarproject.bramble.api.transport.TransportConstants.BOB_HANDSHAKE_TAG_LABEL; import static org.briarproject.bramble.api.transport.TransportConstants.BOB_HEADER_LABEL; import static org.briarproject.bramble.api.transport.TransportConstants.BOB_TAG_LABEL; import static org.briarproject.bramble.api.transport.TransportConstants.ROTATE_LABEL; @@ -24,6 +28,9 @@ import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES; import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES; import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED; import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED; +import static org.briarproject.bramble.util.ByteUtils.writeUint16; +import static org.briarproject.bramble.util.ByteUtils.writeUint64; +import static org.briarproject.bramble.util.StringUtils.toUtf8; class TransportCryptoImpl implements TransportCrypto { @@ -36,45 +43,44 @@ class TransportCryptoImpl implements TransportCrypto { @Override public TransportKeys deriveTransportKeys(TransportId t, - SecretKey master, long rotationPeriod, boolean alice, + SecretKey rootKey, long timePeriod, boolean weAreAlice, boolean active) { - // Keys for the previous period are derived from the master secret - SecretKey inTagPrev = deriveTagKey(master, t, !alice); - SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice); - SecretKey outTagPrev = deriveTagKey(master, t, alice); - SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice); + // Keys for the previous period are derived from the root key + SecretKey inTagPrev = deriveTagKey(rootKey, t, !weAreAlice); + SecretKey inHeaderPrev = deriveHeaderKey(rootKey, t, !weAreAlice); + SecretKey outTagPrev = deriveTagKey(rootKey, t, weAreAlice); + SecretKey outHeaderPrev = deriveHeaderKey(rootKey, t, weAreAlice); // Derive the keys for the current and next periods - SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod); - SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod); - SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1); - SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1); - SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod); - SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod); + SecretKey inTagCurr = rotateKey(inTagPrev, timePeriod); + SecretKey inHeaderCurr = rotateKey(inHeaderPrev, timePeriod); + SecretKey inTagNext = rotateKey(inTagCurr, timePeriod + 1); + SecretKey inHeaderNext = rotateKey(inHeaderCurr, timePeriod + 1); + SecretKey outTagCurr = rotateKey(outTagPrev, timePeriod); + SecretKey outHeaderCurr = rotateKey(outHeaderPrev, timePeriod); // Initialise the reordering windows and stream counters IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev, - rotationPeriod - 1); + timePeriod - 1); IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr, - rotationPeriod); + timePeriod); IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext, - rotationPeriod + 1); + timePeriod + 1); OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr, - rotationPeriod, active); + timePeriod, active); // Collect and return the keys return new TransportKeys(t, inPrev, inCurr, inNext, outCurr); } @Override - public TransportKeys rotateTransportKeys(TransportKeys k, - long rotationPeriod) { - if (k.getRotationPeriod() >= rotationPeriod) return k; + public TransportKeys rotateTransportKeys(TransportKeys k, long timePeriod) { + if (k.getTimePeriod() >= timePeriod) return k; IncomingKeys inPrev = k.getPreviousIncomingKeys(); IncomingKeys inCurr = k.getCurrentIncomingKeys(); IncomingKeys inNext = k.getNextIncomingKeys(); OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); - long startPeriod = outCurr.getRotationPeriod(); + long startPeriod = outCurr.getTimePeriod(); boolean active = outCurr.isActive(); // Rotate the keys - for (long p = startPeriod + 1; p <= rotationPeriod; p++) { + for (long p = startPeriod + 1; p <= timePeriod; p++) { inPrev = inCurr; inCurr = inNext; SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1); @@ -89,24 +95,117 @@ class TransportCryptoImpl implements TransportCrypto { outCurr); } - private SecretKey rotateKey(SecretKey k, long rotationPeriod) { + private SecretKey rotateKey(SecretKey k, long timePeriod) { byte[] period = new byte[INT_64_BYTES]; - ByteUtils.writeUint64(rotationPeriod, period, 0); + writeUint64(timePeriod, period, 0); return crypto.deriveKey(ROTATE_LABEL, k, period); } - private SecretKey deriveTagKey(SecretKey master, TransportId t, - boolean alice) { - String label = alice ? ALICE_TAG_LABEL : BOB_TAG_LABEL; - byte[] id = StringUtils.toUtf8(t.getString()); - return crypto.deriveKey(label, master, id); + private SecretKey deriveTagKey(SecretKey rootKey, TransportId t, + boolean keyBelongsToAlice) { + String label = keyBelongsToAlice ? ALICE_TAG_LABEL : BOB_TAG_LABEL; + byte[] id = toUtf8(t.getString()); + return crypto.deriveKey(label, rootKey, id); } - private SecretKey deriveHeaderKey(SecretKey master, TransportId t, - boolean alice) { - String label = alice ? ALICE_HEADER_LABEL : BOB_HEADER_LABEL; - byte[] id = StringUtils.toUtf8(t.getString()); - return crypto.deriveKey(label, master, id); + private SecretKey deriveHeaderKey(SecretKey rootKey, TransportId t, + boolean keyBelongsToAlice) { + String label = keyBelongsToAlice ? ALICE_HEADER_LABEL : + BOB_HEADER_LABEL; + byte[] id = toUtf8(t.getString()); + return crypto.deriveKey(label, rootKey, id); + } + + @Override + public HandshakeKeys deriveHandshakeKeys(TransportId t, SecretKey rootKey, + long timePeriod, boolean weAreAlice) { + if (timePeriod < 1) throw new IllegalArgumentException(); + IncomingKeys inPrev = deriveIncomingHandshakeKeys(t, rootKey, + weAreAlice, timePeriod - 1); + IncomingKeys inCurr = deriveIncomingHandshakeKeys(t, rootKey, + weAreAlice, timePeriod); + IncomingKeys inNext = deriveIncomingHandshakeKeys(t, rootKey, + weAreAlice, timePeriod + 1); + OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey, + weAreAlice, timePeriod); + return new HandshakeKeys(t, inPrev, inCurr, inNext, outCurr, rootKey, + weAreAlice); + } + + private IncomingKeys deriveIncomingHandshakeKeys(TransportId t, + SecretKey rootKey, boolean weAreAlice, long timePeriod) { + SecretKey tag = deriveHandshakeTagKey(t, rootKey, !weAreAlice, + timePeriod); + SecretKey header = deriveHandshakeHeaderKey(t, rootKey, !weAreAlice, + timePeriod); + return new IncomingKeys(tag, header, timePeriod); + } + + private OutgoingKeys deriveOutgoingHandshakeKeys(TransportId t, + SecretKey rootKey, boolean weAreAlice, long timePeriod) { + SecretKey tag = deriveHandshakeTagKey(t, rootKey, weAreAlice, + timePeriod); + SecretKey header = deriveHandshakeHeaderKey(t, rootKey, weAreAlice, + timePeriod); + return new OutgoingKeys(tag, header, timePeriod, true); + } + + private SecretKey deriveHandshakeTagKey(TransportId t, SecretKey rootKey, + boolean keyBelongsToAlice, long timePeriod) { + String label = keyBelongsToAlice ? ALICE_HANDSHAKE_TAG_LABEL : + BOB_HANDSHAKE_TAG_LABEL; + byte[] id = toUtf8(t.getString()); + byte[] period = new byte[INT_64_BYTES]; + writeUint64(timePeriod, period, 0); + return crypto.deriveKey(label, rootKey, id, period); + } + + private SecretKey deriveHandshakeHeaderKey(TransportId t, SecretKey rootKey, + boolean keyBelongsToAlice, long timePeriod) { + String label = keyBelongsToAlice ? ALICE_HANDSHAKE_HEADER_LABEL : + BOB_HANDSHAKE_HEADER_LABEL; + byte[] id = toUtf8(t.getString()); + byte[] period = new byte[INT_64_BYTES]; + writeUint64(timePeriod, period, 0); + return crypto.deriveKey(label, rootKey, id, period); + } + + @Override + public HandshakeKeys updateHandshakeKeys(HandshakeKeys k, long timePeriod) { + long elapsed = timePeriod - k.getTimePeriod(); + TransportId t = k.getTransportId(); + SecretKey rootKey = k.getRootKey(); + boolean weAreAlice = k.isAlice(); + if (elapsed <= 0) { + // The keys are for the given period or later - don't update them + return k; + } else if (elapsed == 1) { + // The keys are one period old - shift by one period, keeping the + // reordering windows for keys we retain + IncomingKeys inPrev = k.getCurrentIncomingKeys(); + IncomingKeys inCurr = k.getNextIncomingKeys(); + IncomingKeys inNext = deriveIncomingHandshakeKeys(t, rootKey, + weAreAlice, timePeriod + 1); + OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey, + weAreAlice, timePeriod); + return new HandshakeKeys(t, inPrev, inCurr, inNext, outCurr, + rootKey, weAreAlice); + } else if (elapsed == 2) { + // The keys are two periods old - shift by two periods, keeping + // the reordering windows for keys we retain + IncomingKeys inPrev = k.getNextIncomingKeys(); + IncomingKeys inCurr = deriveIncomingHandshakeKeys(t, rootKey, + weAreAlice, timePeriod); + IncomingKeys inNext = deriveIncomingHandshakeKeys(t, rootKey, + weAreAlice, timePeriod + 1); + OutgoingKeys outCurr = deriveOutgoingHandshakeKeys(t, rootKey, + weAreAlice, timePeriod); + return new HandshakeKeys(t, inPrev, inCurr, inNext, outCurr, + rootKey, weAreAlice); + } else { + // The keys are more than two periods old - derive fresh keys + return deriveHandshakeKeys(t, rootKey, timePeriod, weAreAlice); + } } @Override @@ -125,14 +224,14 @@ class TransportCryptoImpl implements TransportCrypto { // The input is the protocol version as a 16-bit integer, followed by // the stream number as a 64-bit integer byte[] protocolVersionBytes = new byte[INT_16_BYTES]; - ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0); + writeUint16(protocolVersion, protocolVersionBytes, 0); prf.update(protocolVersionBytes, 0, protocolVersionBytes.length); byte[] streamNumberBytes = new byte[INT_64_BYTES]; - ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0); + writeUint64(streamNumber, streamNumberBytes, 0); prf.update(streamNumberBytes, 0, streamNumberBytes.length); byte[] mac = new byte[macLength]; prf.doFinal(mac, 0); // The output is the first TAG_LENGTH bytes of the MAC - System.arraycopy(mac, 0, tag, 0, TAG_LENGTH); + arraycopy(mac, 0, tag, 0, TAG_LENGTH); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java index 958da7907..c63760177 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java @@ -2,9 +2,13 @@ package org.briarproject.bramble.db; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.contact.PendingContactId; +import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DataTooNewException; import org.briarproject.bramble.api.db.DataTooOldException; +import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.MessageDeletedException; import org.briarproject.bramble.api.db.Metadata; @@ -23,8 +27,11 @@ import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.validation.MessageState; -import org.briarproject.bramble.api.transport.KeySet; -import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.transport.HandshakeKeySet; +import org.briarproject.bramble.api.transport.HandshakeKeySetId; +import org.briarproject.bramble.api.transport.HandshakeKeys; +import org.briarproject.bramble.api.transport.TransportKeySet; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.api.transport.TransportKeys; import java.util.Collection; @@ -33,11 +40,14 @@ import java.util.Map; import javax.annotation.Nullable; /** - * A low-level interface to the database (DatabaseComponent provides a - * high-level interface). Most operations take a transaction argument, which is - * obtained by calling {@link #startTransaction()}. Every transaction must be - * terminated by calling either {@link #abortTransaction(Object) abortTransaction(T)} or - * {@link #commitTransaction(Object) commitTransaction(T)}, even if an exception is thrown. + * A low-level interface to the database ({@link DatabaseComponent} provides a + * high-level interface). + *

+ * Most operations take a transaction argument, which is obtained by calling + * {@link #startTransaction()}. Every transaction must be terminated by calling + * either {@link #abortTransaction(Object) abortTransaction(T)} or + * {@link #commitTransaction(Object) commitTransaction(T)}, even if an + * exception is thrown. */ @NotNullByDefault interface Database { @@ -95,6 +105,20 @@ interface Database { void addGroupVisibility(T txn, ContactId c, GroupId g, boolean shared) throws DbException; + /** + * Stores the given handshake keys for the given contact and returns a + * key set ID. + */ + HandshakeKeySetId addHandshakeKeys(T txn, ContactId c, HandshakeKeys k) + throws DbException; + + /** + * Stores the given handshake keys for the given pending contact and + * returns a key set ID. + */ + HandshakeKeySetId addHandshakeKeys(T txn, PendingContactId p, + HandshakeKeys k) throws DbException; + /** * Stores a local pseudonym. */ @@ -121,6 +145,11 @@ interface Database { */ void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException; + /** + * Stores a pending contact. + */ + void addPendingContact(T txn, PendingContact p) throws DbException; + /** * Stores a transport. */ @@ -131,7 +160,7 @@ interface Database { * Stores the given transport keys for the given contact and returns a * key set ID. */ - KeySetId addTransportKeys(T txn, ContactId c, TransportKeys k) + TransportKeySetId addTransportKeys(T txn, ContactId c, TransportKeys k) throws DbException; /** @@ -171,6 +200,14 @@ interface Database { */ boolean containsMessage(T txn, MessageId m) throws DbException; + /** + * Returns true if the database contains the given pending contact. + *

+ * Read-only. + */ + boolean containsPendingContact(T txn, PendingContactId p) + throws DbException; + /** * Returns true if the database contains the given transport. *

@@ -277,6 +314,14 @@ interface Database { Map getGroupVisibility(T txn, GroupId g) throws DbException; + /** + * Returns all handshake keys for the given transport. + *

+ * Read-only. + */ + Collection getHandshakeKeys(T txn, TransportId t) + throws DbException; + /** * Returns the local pseudonym with the given ID. *

@@ -467,6 +512,13 @@ interface Database { */ long getNextSendTime(T txn, ContactId c) throws DbException; + /** + * Returns all pending contacts. + *

+ * Read-only. + */ + Collection getPendingContacts(T txn) throws DbException; + /** * Returns the IDs of some messages that are eligible to be sent to the * given contact and have been requested by the contact, up to the given @@ -489,13 +541,19 @@ interface Database { *

* Read-only. */ - Collection getTransportKeys(T txn, TransportId t) + Collection getTransportKeys(T txn, TransportId t) + throws DbException; + + /** + * Increments the outgoing stream counter for the given handshake keys. + */ + void incrementStreamCounter(T txn, TransportId t, HandshakeKeySetId k) throws DbException; /** * Increments the outgoing stream counter for the given transport keys. */ - void incrementStreamCounter(T txn, TransportId t, KeySetId k) + void incrementStreamCounter(T txn, TransportId t, TransportKeySetId k) throws DbException; /** @@ -564,6 +622,12 @@ interface Database { void removeGroupVisibility(T txn, ContactId c, GroupId g) throws DbException; + /** + * Removes the given handshake keys from the database. + */ + void removeHandshakeKeys(T txn, TransportId t, HandshakeKeySetId k) + throws DbException; + /** * Removes a local pseudonym (and all associated state) from the database. */ @@ -581,6 +645,11 @@ interface Database { void removeOfferedMessages(T txn, ContactId c, Collection requested) throws DbException; + /** + * Removes a pending contact (and all associated state) from the database. + */ + void removePendingContact(T txn, PendingContactId p) throws DbException; + /** * Removes a transport (and all associated state) from the database. */ @@ -589,7 +658,7 @@ interface Database { /** * Removes the given transport keys from the database. */ - void removeTransportKeys(T txn, TransportId t, KeySetId k) + void removeTransportKeys(T txn, TransportId t, TransportKeySetId k) throws DbException; /** @@ -634,16 +703,29 @@ interface Database { throws DbException; /** - * Sets the reordering window for the given key set and transport in the - * given rotation period. + * Sets the state of the given pending contact. */ - void setReorderingWindow(T txn, KeySetId k, TransportId t, - long rotationPeriod, long base, byte[] bitmap) throws DbException; + void setPendingContactState(T txn, PendingContactId p, + PendingContactState state) throws DbException; + + /** + * Sets the reordering window for the given transport key set in the given + * time period. + */ + void setReorderingWindow(T txn, TransportKeySetId k, TransportId t, + long timePeriod, long base, byte[] bitmap) throws DbException; + + /** + * Sets the reordering window for the given handshake key set in the given + * time period. + */ + void setReorderingWindow(T txn, HandshakeKeySetId k, TransportId t, + long timePeriod, long base, byte[] bitmap) throws DbException; /** * Marks the given transport keys as usable for outgoing streams. */ - void setTransportKeysActive(T txn, TransportId t, KeySetId k) + void setTransportKeysActive(T txn, TransportId t, TransportKeySetId k) throws DbException; /** @@ -654,8 +736,13 @@ interface Database { void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, int maxLatency) throws DbException; + /** + * Updates the given handshake keys. + */ + void updateHandshakeKeys(T txn, HandshakeKeySet ks) throws DbException; + /** * Updates the given transport keys following key rotation. */ - void updateTransportKeys(T txn, KeySet ks) throws DbException; + void updateTransportKeys(T txn, TransportKeySet ks) throws DbException; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index a60d26c57..b92ab8660 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -2,6 +2,8 @@ package org.briarproject.bramble.db; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.event.ContactAddedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent; @@ -21,8 +23,10 @@ import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.NoSuchLocalAuthorException; import org.briarproject.bramble.api.db.NoSuchMessageException; +import org.briarproject.bramble.api.db.NoSuchPendingContactException; import org.briarproject.bramble.api.db.NoSuchTransportException; import org.briarproject.bramble.api.db.NullableDbCallable; +import org.briarproject.bramble.api.db.PendingContactExistsException; import org.briarproject.bramble.api.db.TaskAction; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.event.EventBus; @@ -59,8 +63,11 @@ import org.briarproject.bramble.api.sync.event.MessageToRequestEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesSentEvent; import org.briarproject.bramble.api.sync.validation.MessageState; -import org.briarproject.bramble.api.transport.KeySet; -import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.transport.HandshakeKeySet; +import org.briarproject.bramble.api.transport.HandshakeKeySetId; +import org.briarproject.bramble.api.transport.HandshakeKeys; +import org.briarproject.bramble.api.transport.TransportKeySet; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.api.transport.TransportKeys; import java.util.ArrayList; @@ -253,6 +260,30 @@ class DatabaseComponentImpl implements DatabaseComponent { } } + @Override + public HandshakeKeySetId addHandshakeKeys(Transaction transaction, + ContactId c, HandshakeKeys k) throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if (!db.containsTransport(txn, k.getTransportId())) + throw new NoSuchTransportException(); + return db.addHandshakeKeys(txn, c, k); + } + + @Override + public HandshakeKeySetId addHandshakeKeys(Transaction transaction, + PendingContactId p, HandshakeKeys k) throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsPendingContact(txn, p)) + throw new NoSuchPendingContactException(); + if (!db.containsTransport(txn, k.getTransportId())) + throw new NoSuchTransportException(); + return db.addHandshakeKeys(txn, p, k); + } + @Override public void addLocalAuthor(Transaction transaction, LocalAuthor a) throws DbException { @@ -281,6 +312,16 @@ class DatabaseComponentImpl implements DatabaseComponent { db.mergeMessageMetadata(txn, m.getId(), meta); } + @Override + public void addPendingContact(Transaction transaction, PendingContact p) + throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (db.containsPendingContact(txn, p.getId())) + throw new PendingContactExistsException(); + db.addPendingContact(txn, p); + } + @Override public void addTransport(Transaction transaction, TransportId t, int maxLatency) throws DbException { @@ -291,8 +332,8 @@ class DatabaseComponentImpl implements DatabaseComponent { } @Override - public KeySetId addTransportKeys(Transaction transaction, ContactId c, - TransportKeys k) throws DbException { + public TransportKeySetId addTransportKeys(Transaction transaction, + ContactId c, TransportKeys k) throws DbException { if (transaction.isReadOnly()) throw new IllegalArgumentException(); T txn = unbox(transaction); if (!db.containsContact(txn, c)) @@ -325,6 +366,13 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.containsLocalAuthor(txn, local); } + @Override + public boolean containsPendingContact(Transaction transaction, + PendingContactId p) throws DbException { + T txn = unbox(transaction); + return db.containsPendingContact(txn, p); + } + @Override public void deleteMessage(Transaction transaction, MessageId m) throws DbException { @@ -498,6 +546,15 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.getGroupVisibility(txn, c, g); } + @Override + public Collection getHandshakeKeys(Transaction transaction, + TransportId t) throws DbException { + T txn = unbox(transaction); + if (!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + return db.getHandshakeKeys(txn, t); + } + @Override public LocalAuthor getLocalAuthor(Transaction transaction, AuthorId a) throws DbException { @@ -655,6 +712,13 @@ class DatabaseComponentImpl implements DatabaseComponent { return db.getNextSendTime(txn, c); } + @Override + public Collection getPendingContacts( + Transaction transaction) throws DbException { + T txn = unbox(transaction); + return db.getPendingContacts(txn); + } + @Override public Settings getSettings(Transaction transaction, String namespace) throws DbException { @@ -663,7 +727,7 @@ class DatabaseComponentImpl implements DatabaseComponent { } @Override - public Collection getTransportKeys(Transaction transaction, + public Collection getTransportKeys(Transaction transaction, TransportId t) throws DbException { T txn = unbox(transaction); if (!db.containsTransport(txn, t)) @@ -673,7 +737,17 @@ class DatabaseComponentImpl implements DatabaseComponent { @Override public void incrementStreamCounter(Transaction transaction, TransportId t, - KeySetId k) throws DbException { + HandshakeKeySetId k) throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.incrementStreamCounter(txn, t, k); + } + + @Override + public void incrementStreamCounter(Transaction transaction, TransportId t, + TransportKeySetId k) throws DbException { if (transaction.isReadOnly()) throw new IllegalArgumentException(); T txn = unbox(transaction); if (!db.containsTransport(txn, t)) @@ -822,6 +896,16 @@ class DatabaseComponentImpl implements DatabaseComponent { transaction.attach(new GroupVisibilityUpdatedEvent(affected)); } + @Override + public void removeHandshakeKeys(Transaction transaction, + TransportId t, HandshakeKeySetId k) throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.removeHandshakeKeys(txn, t, k); + } + @Override public void removeLocalAuthor(Transaction transaction, AuthorId a) throws DbException { @@ -844,6 +928,16 @@ class DatabaseComponentImpl implements DatabaseComponent { db.removeMessage(txn, m); } + @Override + public void removePendingContact(Transaction transaction, + PendingContactId p) throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsPendingContact(txn, p)) + throw new NoSuchPendingContactException(); + db.removePendingContact(txn, p); + } + @Override public void removeTransport(Transaction transaction, TransportId t) throws DbException { @@ -856,7 +950,7 @@ class DatabaseComponentImpl implements DatabaseComponent { @Override public void removeTransportKeys(Transaction transaction, - TransportId t, KeySetId k) throws DbException { + TransportId t, TransportKeySetId k) throws DbException { if (transaction.isReadOnly()) throw new IllegalArgumentException(); T txn = unbox(transaction); if (!db.containsTransport(txn, t)) @@ -955,19 +1049,30 @@ class DatabaseComponentImpl implements DatabaseComponent { } @Override - public void setReorderingWindow(Transaction transaction, KeySetId k, - TransportId t, long rotationPeriod, long base, byte[] bitmap) - throws DbException { + public void setReorderingWindow(Transaction transaction, + TransportKeySetId k, TransportId t, long timePeriod, long base, + byte[] bitmap) throws DbException { if (transaction.isReadOnly()) throw new IllegalArgumentException(); T txn = unbox(transaction); if (!db.containsTransport(txn, t)) throw new NoSuchTransportException(); - db.setReorderingWindow(txn, k, t, rotationPeriod, base, bitmap); + db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap); + } + + @Override + public void setReorderingWindow(Transaction transaction, + HandshakeKeySetId k, TransportId t, long timePeriod, long base, + byte[] bitmap) throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap); } @Override public void setTransportKeysActive(Transaction transaction, TransportId t, - KeySetId k) throws DbException { + TransportKeySetId k) throws DbException { if (transaction.isReadOnly()) throw new IllegalArgumentException(); T txn = unbox(transaction); if (!db.containsTransport(txn, t)) @@ -976,12 +1081,24 @@ class DatabaseComponentImpl implements DatabaseComponent { } @Override - public void updateTransportKeys(Transaction transaction, - Collection keys) throws DbException { + public void updateHandshakeKeys(Transaction transaction, + Collection keys) throws DbException { if (transaction.isReadOnly()) throw new IllegalArgumentException(); T txn = unbox(transaction); - for (KeySet ks : keys) { - TransportId t = ks.getTransportKeys().getTransportId(); + for (HandshakeKeySet ks : keys) { + TransportId t = ks.getKeys().getTransportId(); + if (db.containsTransport(txn, t)) + db.updateHandshakeKeys(txn, ks); + } + } + + @Override + public void updateTransportKeys(Transaction transaction, + Collection keys) throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + for (TransportKeySet ks : keys) { + TransportId t = ks.getKeys().getTransportId(); if (db.containsTransport(txn, t)) db.updateTransportKeys(txn, ks); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index 7da00445c..6ed18c229 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -2,6 +2,9 @@ package org.briarproject.bramble.db; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.contact.PendingContactId; +import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DataTooNewException; import org.briarproject.bramble.api.db.DataTooOldException; @@ -26,10 +29,13 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.transport.HandshakeKeySet; +import org.briarproject.bramble.api.transport.HandshakeKeySetId; +import org.briarproject.bramble.api.transport.HandshakeKeys; import org.briarproject.bramble.api.transport.IncomingKeys; -import org.briarproject.bramble.api.transport.KeySet; -import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.OutgoingKeys; +import org.briarproject.bramble.api.transport.TransportKeySet; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.api.transport.TransportKeys; import java.sql.Connection; @@ -55,6 +61,7 @@ import java.util.logging.Logger; import javax.annotation.Nullable; +import static java.sql.Types.BINARY; import static java.sql.Types.INTEGER; import static java.sql.Types.VARCHAR; import static java.util.logging.Level.INFO; @@ -85,9 +92,9 @@ import static org.briarproject.bramble.util.LogUtils.now; abstract class JdbcDatabase implements Database { // Package access for testing - static final int CODE_SCHEMA_VERSION = 41; + static final int CODE_SCHEMA_VERSION = 42; - // Rotation period offsets for incoming transport keys + // Time period offsets for incoming transport keys private static final int OFFSET_PREV = -1; private static final int OFFSET_CURR = 0; private static final int OFFSET_NEXT = 1; @@ -248,7 +255,7 @@ abstract class JdbcDatabase implements Database { "CREATE TABLE outgoingKeys" + " (transportId _STRING NOT NULL," + " keySetId _COUNTER," - + " rotationPeriod BIGINT NOT NULL," + + " timePeriod BIGINT NOT NULL," + " contactId INT NOT NULL," + " tagKey _SECRET NOT NULL," + " headerKey _SECRET NOT NULL," @@ -267,8 +274,7 @@ abstract class JdbcDatabase implements Database { "CREATE TABLE incomingKeys" + " (transportId _STRING NOT NULL," + " keySetId INT NOT NULL," - + " rotationPeriod BIGINT NOT NULL," - + " contactId INT NOT NULL," + + " timePeriod BIGINT NOT NULL," + " tagKey _SECRET NOT NULL," + " headerKey _SECRET NOT NULL," + " base BIGINT NOT NULL," @@ -280,9 +286,57 @@ abstract class JdbcDatabase implements Database { + " ON DELETE CASCADE," + " FOREIGN KEY (keySetId)" + " REFERENCES outgoingKeys (keySetId)" + + " ON DELETE CASCADE)"; + + private static final String CREATE_PENDING_CONTACTS = + "CREATE TABLE pendingContacts" + + " (pendingContactId _HASH NOT NULL," + + " publicKey _BINARY NOT NULL," + + " alias _STRING NOT NULL," + + " state INT NOT NULL," + + " timestamp BIGINT NOT NULL," + + " PRIMARY KEY (pendingContactId))"; + + private static final String CREATE_OUTGOING_HANDSHAKE_KEYS = + "CREATE TABLE outgoingHandshakeKeys" + + " (transportId _STRING NOT NULL," + + " keySetId _COUNTER," + + " timePeriod BIGINT NOT NULL," + + " contactId INT," // Null if contact is pending + + " pendingContactId _HASH," // Null if not pending + + " rootKey _SECRET NOT NULL," + + " alice BOOLEAN NOT NULL," + + " tagKey _SECRET NOT NULL," + + " headerKey _SECRET NOT NULL," + + " stream BIGINT NOT NULL," + + " PRIMARY KEY (transportId, keySetId)," + + " FOREIGN KEY (transportId)" + + " REFERENCES transports (transportId)" + " ON DELETE CASCADE," + + " UNIQUE (keySetId)," + " FOREIGN KEY (contactId)" + " REFERENCES contacts (contactId)" + + " ON DELETE CASCADE," + + " FOREIGN KEY (pendingContactId)" + + " REFERENCES pendingContacts (pendingContactId)" + + " ON DELETE CASCADE)"; + + private static final String CREATE_INCOMING_HANDSHAKE_KEYS = + "CREATE TABLE incomingHandshakeKeys" + + " (transportId _STRING NOT NULL," + + " keySetId INT NOT NULL," + + " timePeriod BIGINT NOT NULL," + + " tagKey _SECRET NOT NULL," + + " headerKey _SECRET NOT NULL," + + " base BIGINT NOT NULL," + + " bitmap _BINARY NOT NULL," + + " periodOffset INT NOT NULL," + + " PRIMARY KEY (transportId, keySetId, periodOffset)," + + " FOREIGN KEY (transportId)" + + " REFERENCES transports (transportId)" + + " ON DELETE CASCADE," + + " FOREIGN KEY (keySetId)" + + " REFERENCES outgoingHandshakeKeys (keySetId)" + " ON DELETE CASCADE)"; private static final String INDEX_CONTACTS_BY_AUTHOR_ID = @@ -428,7 +482,8 @@ abstract class JdbcDatabase implements Database { return Arrays.asList( new Migration38_39(), new Migration39_40(), - new Migration40_41(dbTypes) + new Migration40_41(dbTypes), + new Migration41_42(dbTypes) ); } @@ -478,6 +533,11 @@ abstract class JdbcDatabase implements Database { s.executeUpdate(dbTypes.replaceTypes(CREATE_TRANSPORTS)); s.executeUpdate(dbTypes.replaceTypes(CREATE_OUTGOING_KEYS)); s.executeUpdate(dbTypes.replaceTypes(CREATE_INCOMING_KEYS)); + s.executeUpdate(dbTypes.replaceTypes(CREATE_PENDING_CONTACTS)); + s.executeUpdate(dbTypes.replaceTypes( + CREATE_OUTGOING_HANDSHAKE_KEYS)); + s.executeUpdate(dbTypes.replaceTypes( + CREATE_INCOMING_HANDSHAKE_KEYS)); s.close(); } catch (SQLException e) { tryToClose(s, LOG, WARNING); @@ -715,6 +775,103 @@ abstract class JdbcDatabase implements Database { } } + @Override + public HandshakeKeySetId addHandshakeKeys(Connection txn, ContactId c, + HandshakeKeys k) throws DbException { + return addHandshakeKeys(txn, c, null, k); + } + + @Override + public HandshakeKeySetId addHandshakeKeys(Connection txn, + PendingContactId p, HandshakeKeys k) throws DbException { + return addHandshakeKeys(txn, null, p, k); + } + + private HandshakeKeySetId addHandshakeKeys(Connection txn, + @Nullable ContactId c, @Nullable PendingContactId p, + HandshakeKeys k) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + // Store the outgoing keys + String sql = "INSERT INTO outgoingHandshakeKeys (contactId," + + " pendingContactId, transportId, rootKey, alice," + + " timePeriod, tagKey, headerKey, stream)" + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + ps = txn.prepareStatement(sql); + if (c == null) ps.setNull(1, INTEGER); + else ps.setInt(1, c.getInt()); + if (p == null) ps.setNull(2, BINARY); + else ps.setBytes(2, p.getBytes()); + ps.setString(3, k.getTransportId().getString()); + ps.setBytes(4, k.getRootKey().getBytes()); + ps.setBoolean(5, k.isAlice()); + OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); + ps.setLong(6, outCurr.getTimePeriod()); + ps.setBytes(7, outCurr.getTagKey().getBytes()); + ps.setBytes(8, outCurr.getHeaderKey().getBytes()); + ps.setLong(9, outCurr.getStreamCounter()); + int affected = ps.executeUpdate(); + if (affected != 1) throw new DbStateException(); + ps.close(); + // Get the new (highest) key set ID + sql = "SELECT keySetId FROM outgoingHandshakeKeys" + + " ORDER BY keySetId DESC LIMIT 1"; + ps = txn.prepareStatement(sql); + rs = ps.executeQuery(); + if (!rs.next()) throw new DbStateException(); + HandshakeKeySetId keySetId = new HandshakeKeySetId(rs.getInt(1)); + if (rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + // Store the incoming keys + sql = "INSERT INTO incomingHandshakeKeys (keySetId, transportId," + + " timePeriod, tagKey, headerKey, base, bitmap," + + " periodOffset)" + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + ps = txn.prepareStatement(sql); + ps.setInt(1, keySetId.getInt()); + ps.setString(2, k.getTransportId().getString()); + // Previous time period + IncomingKeys inPrev = k.getPreviousIncomingKeys(); + ps.setLong(3, inPrev.getTimePeriod()); + ps.setBytes(4, inPrev.getTagKey().getBytes()); + ps.setBytes(5, inPrev.getHeaderKey().getBytes()); + ps.setLong(6, inPrev.getWindowBase()); + ps.setBytes(7, inPrev.getWindowBitmap()); + ps.setInt(8, OFFSET_PREV); + ps.addBatch(); + // Current time period + IncomingKeys inCurr = k.getCurrentIncomingKeys(); + ps.setLong(3, inCurr.getTimePeriod()); + ps.setBytes(4, inCurr.getTagKey().getBytes()); + ps.setBytes(5, inCurr.getHeaderKey().getBytes()); + ps.setLong(6, inCurr.getWindowBase()); + ps.setBytes(7, inCurr.getWindowBitmap()); + ps.setInt(8, OFFSET_CURR); + ps.addBatch(); + // Next time period + IncomingKeys inNext = k.getNextIncomingKeys(); + ps.setLong(3, inNext.getTimePeriod()); + ps.setBytes(4, inNext.getTagKey().getBytes()); + ps.setBytes(5, inNext.getHeaderKey().getBytes()); + ps.setLong(6, inNext.getWindowBase()); + ps.setBytes(7, inNext.getWindowBitmap()); + ps.setInt(8, OFFSET_NEXT); + ps.addBatch(); + int[] batchAffected = ps.executeBatch(); + if (batchAffected.length != 3) throw new DbStateException(); + for (int rows : batchAffected) + if (rows != 1) throw new DbStateException(); + ps.close(); + return keySetId; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public void addLocalAuthor(Connection txn, LocalAuthor a) throws DbException { @@ -895,6 +1052,29 @@ abstract class JdbcDatabase implements Database { } } + @Override + public void addPendingContact(Connection txn, PendingContact p) + throws DbException { + PreparedStatement ps = null; + try { + String sql = "INSERT INTO pendingContacts (pendingContactId," + + " publicKey, alias, state, timestamp)" + + " VALUES (?, ?, ?, ?, ?)"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, p.getId().getBytes()); + ps.setBytes(2, p.getPublicKey()); + ps.setString(3, p.getAlias()); + ps.setInt(4, p.getState().getValue()); + ps.setLong(5, p.getTimestamp()); + int affected = ps.executeUpdate(); + if (affected != 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public void addTransport(Connection txn, TransportId t, int maxLatency) throws DbException { @@ -915,20 +1095,20 @@ abstract class JdbcDatabase implements Database { } @Override - public KeySetId addTransportKeys(Connection txn, ContactId c, + public TransportKeySetId addTransportKeys(Connection txn, ContactId c, TransportKeys k) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { // Store the outgoing keys String sql = "INSERT INTO outgoingKeys (contactId, transportId," - + " rotationPeriod, tagKey, headerKey, stream, active)" + + " timePeriod, tagKey, headerKey, stream, active)" + " VALUES (?, ?, ?, ?, ?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); ps.setString(2, k.getTransportId().getString()); OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); - ps.setLong(3, outCurr.getRotationPeriod()); + ps.setLong(3, outCurr.getTimePeriod()); ps.setBytes(4, outCurr.getTagKey().getBytes()); ps.setBytes(5, outCurr.getHeaderKey().getBytes()); ps.setLong(6, outCurr.getStreamCounter()); @@ -942,45 +1122,44 @@ abstract class JdbcDatabase implements Database { ps = txn.prepareStatement(sql); rs = ps.executeQuery(); if (!rs.next()) throw new DbStateException(); - KeySetId keySetId = new KeySetId(rs.getInt(1)); + TransportKeySetId keySetId = new TransportKeySetId(rs.getInt(1)); if (rs.next()) throw new DbStateException(); rs.close(); ps.close(); // Store the incoming keys - sql = "INSERT INTO incomingKeys (keySetId, contactId, transportId," - + " rotationPeriod, tagKey, headerKey, base, bitmap," + sql = "INSERT INTO incomingKeys (keySetId, transportId," + + " timePeriod, tagKey, headerKey, base, bitmap," + " periodOffset)" - + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setInt(1, keySetId.getInt()); - ps.setInt(2, c.getInt()); - ps.setString(3, k.getTransportId().getString()); - // Previous rotation period + ps.setString(2, k.getTransportId().getString()); + // Previous time period IncomingKeys inPrev = k.getPreviousIncomingKeys(); - ps.setLong(4, inPrev.getRotationPeriod()); - ps.setBytes(5, inPrev.getTagKey().getBytes()); - ps.setBytes(6, inPrev.getHeaderKey().getBytes()); - ps.setLong(7, inPrev.getWindowBase()); - ps.setBytes(8, inPrev.getWindowBitmap()); - ps.setInt(9, OFFSET_PREV); + ps.setLong(3, inPrev.getTimePeriod()); + ps.setBytes(4, inPrev.getTagKey().getBytes()); + ps.setBytes(5, inPrev.getHeaderKey().getBytes()); + ps.setLong(6, inPrev.getWindowBase()); + ps.setBytes(7, inPrev.getWindowBitmap()); + ps.setInt(8, OFFSET_PREV); ps.addBatch(); - // Current rotation period + // Current time period IncomingKeys inCurr = k.getCurrentIncomingKeys(); - ps.setLong(4, inCurr.getRotationPeriod()); - ps.setBytes(5, inCurr.getTagKey().getBytes()); - ps.setBytes(6, inCurr.getHeaderKey().getBytes()); - ps.setLong(7, inCurr.getWindowBase()); - ps.setBytes(8, inCurr.getWindowBitmap()); - ps.setInt(9, OFFSET_CURR); + ps.setLong(3, inCurr.getTimePeriod()); + ps.setBytes(4, inCurr.getTagKey().getBytes()); + ps.setBytes(5, inCurr.getHeaderKey().getBytes()); + ps.setLong(6, inCurr.getWindowBase()); + ps.setBytes(7, inCurr.getWindowBitmap()); + ps.setInt(8, OFFSET_CURR); ps.addBatch(); - // Next rotation period + // Next time period IncomingKeys inNext = k.getNextIncomingKeys(); - ps.setLong(4, inNext.getRotationPeriod()); - ps.setBytes(5, inNext.getTagKey().getBytes()); - ps.setBytes(6, inNext.getHeaderKey().getBytes()); - ps.setLong(7, inNext.getWindowBase()); - ps.setBytes(8, inNext.getWindowBitmap()); - ps.setInt(9, OFFSET_NEXT); + ps.setLong(3, inNext.getTimePeriod()); + ps.setBytes(4, inNext.getTagKey().getBytes()); + ps.setBytes(5, inNext.getHeaderKey().getBytes()); + ps.setLong(6, inNext.getWindowBase()); + ps.setBytes(7, inNext.getWindowBitmap()); + ps.setInt(8, OFFSET_NEXT); ps.addBatch(); int[] batchAffected = ps.executeBatch(); if (batchAffected.length != 3) throw new DbStateException(); @@ -1107,6 +1286,29 @@ abstract class JdbcDatabase implements Database { } } + @Override + public boolean containsPendingContact(Connection txn, PendingContactId p) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT NULL FROM pendingContacts" + + " WHERE pendingContactId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, p.getBytes()); + rs = ps.executeQuery(); + boolean found = rs.next(); + if (rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + return found; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public boolean containsTransport(Connection txn, TransportId t) throws DbException { @@ -1256,14 +1458,14 @@ abstract class JdbcDatabase implements Database { @Override public Collection getContacts(Connection txn) throws DbException { - PreparedStatement ps = null; + Statement s = null; ResultSet rs = null; try { String sql = "SELECT contactId, authorId, formatVersion, name," + " alias, publicKey, localAuthorId, verified, active" + " FROM contacts"; - ps = txn.prepareStatement(sql); - rs = ps.executeQuery(); + s = txn.createStatement(); + rs = s.executeQuery(sql); List contacts = new ArrayList<>(); while (rs.next()) { ContactId contactId = new ContactId(rs.getInt(1)); @@ -1281,11 +1483,11 @@ abstract class JdbcDatabase implements Database { alias, verified, active)); } rs.close(); - ps.close(); + s.close(); return contacts; } catch (SQLException e) { tryToClose(rs, LOG, WARNING); - tryToClose(ps, LOG, WARNING); + tryToClose(s, LOG, WARNING); throw new DbException(e); } } @@ -1485,6 +1687,86 @@ abstract class JdbcDatabase implements Database { } } + @Override + public Collection getHandshakeKeys(Connection txn, + TransportId t) throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + // Retrieve the incoming keys + String sql = "SELECT timePeriod, tagKey, headerKey, base, bitmap" + + " FROM incomingHandshakeKeys" + + " WHERE transportId = ?" + + " ORDER BY keySetId, periodOffset"; + ps = txn.prepareStatement(sql); + ps.setString(1, t.getString()); + rs = ps.executeQuery(); + List inKeys = new ArrayList<>(); + while (rs.next()) { + long timePeriod = rs.getLong(1); + SecretKey tagKey = new SecretKey(rs.getBytes(2)); + SecretKey headerKey = new SecretKey(rs.getBytes(3)); + long windowBase = rs.getLong(4); + byte[] windowBitmap = rs.getBytes(5); + inKeys.add(new IncomingKeys(tagKey, headerKey, timePeriod, + windowBase, windowBitmap)); + } + rs.close(); + ps.close(); + // Retrieve the outgoing keys in the same order + sql = "SELECT keySetId, contactId, pendingContactId, timePeriod," + + " tagKey, headerKey, rootKey, alice, stream" + + " FROM outgoingHandshakeKeys" + + " WHERE transportId = ?" + + " ORDER BY keySetId"; + ps = txn.prepareStatement(sql); + ps.setString(1, t.getString()); + rs = ps.executeQuery(); + Collection keys = new ArrayList<>(); + for (int i = 0; rs.next(); i++) { + // There should be three times as many incoming keys + if (inKeys.size() < (i + 1) * 3) throw new DbStateException(); + HandshakeKeySetId keySetId = + new HandshakeKeySetId(rs.getInt(1)); + ContactId contactId = null; + int cId = rs.getInt(2); + if (!rs.wasNull()) contactId = new ContactId(cId); + PendingContactId pendingContactId = null; + byte[] pId = rs.getBytes(3); + if (!rs.wasNull()) pendingContactId = new PendingContactId(pId); + long timePeriod = rs.getLong(4); + SecretKey tagKey = new SecretKey(rs.getBytes(5)); + SecretKey headerKey = new SecretKey(rs.getBytes(6)); + SecretKey rootKey = new SecretKey(rs.getBytes(7)); + boolean alice = rs.getBoolean(8); + long streamCounter = rs.getLong(9); + OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey, + timePeriod, streamCounter, true); + IncomingKeys inPrev = inKeys.get(i * 3); + IncomingKeys inCurr = inKeys.get(i * 3 + 1); + IncomingKeys inNext = inKeys.get(i * 3 + 2); + HandshakeKeys handshakeKeys = new HandshakeKeys(t, inPrev, + inCurr, inNext, outCurr, rootKey, alice); + if (contactId == null) { + if (pendingContactId == null) throw new DbStateException(); + keys.add(new HandshakeKeySet(keySetId, pendingContactId, + handshakeKeys)); + } else { + if (pendingContactId != null) throw new DbStateException(); + keys.add(new HandshakeKeySet(keySetId, contactId, + handshakeKeys)); + } + } + rs.close(); + ps.close(); + return keys; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public Collection getLocalAuthors(Connection txn) throws DbException { @@ -2086,6 +2368,38 @@ abstract class JdbcDatabase implements Database { } } + @Override + public Collection getPendingContacts(Connection txn) + throws DbException { + Statement s = null; + ResultSet rs = null; + try { + String sql = "SELECT pendingContactId, publicKey, alias, state," + + " timestamp" + + " FROM pendingContacts"; + s = txn.createStatement(); + rs = s.executeQuery(sql); + List pendingContacts = new ArrayList<>(); + while (rs.next()) { + PendingContactId id = new PendingContactId(rs.getBytes(1)); + byte[] publicKey = rs.getBytes(2); + String alias = rs.getString(3); + PendingContactState state = + PendingContactState.fromValue(rs.getInt(4)); + long timestamp = rs.getLong(5); + pendingContacts.add(new PendingContact(id, publicKey, alias, + state, timestamp)); + } + rs.close(); + s.close(); + return pendingContacts; + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(s, LOG, WARNING); + throw new DbException(e); + } + } + @Override public Collection getRequestedMessagesToSend(Connection txn, ContactId c, int maxLength, int maxLatency) throws DbException { @@ -2149,14 +2463,13 @@ abstract class JdbcDatabase implements Database { } @Override - public Collection getTransportKeys(Connection txn, TransportId t) - throws DbException { + public Collection getTransportKeys(Connection txn, + TransportId t) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { // Retrieve the incoming keys - String sql = "SELECT rotationPeriod, tagKey, headerKey," - + " base, bitmap" + String sql = "SELECT timePeriod, tagKey, headerKey, base, bitmap" + " FROM incomingKeys" + " WHERE transportId = ?" + " ORDER BY keySetId, periodOffset"; @@ -2165,18 +2478,18 @@ abstract class JdbcDatabase implements Database { rs = ps.executeQuery(); List inKeys = new ArrayList<>(); while (rs.next()) { - long rotationPeriod = rs.getLong(1); + long timePeriod = rs.getLong(1); SecretKey tagKey = new SecretKey(rs.getBytes(2)); SecretKey headerKey = new SecretKey(rs.getBytes(3)); long windowBase = rs.getLong(4); byte[] windowBitmap = rs.getBytes(5); - inKeys.add(new IncomingKeys(tagKey, headerKey, rotationPeriod, + inKeys.add(new IncomingKeys(tagKey, headerKey, timePeriod, windowBase, windowBitmap)); } rs.close(); ps.close(); // Retrieve the outgoing keys in the same order - sql = "SELECT keySetId, contactId, rotationPeriod," + sql = "SELECT keySetId, contactId, timePeriod," + " tagKey, headerKey, stream, active" + " FROM outgoingKeys" + " WHERE transportId = ?" @@ -2184,25 +2497,27 @@ abstract class JdbcDatabase implements Database { ps = txn.prepareStatement(sql); ps.setString(1, t.getString()); rs = ps.executeQuery(); - Collection keys = new ArrayList<>(); + Collection keys = new ArrayList<>(); for (int i = 0; rs.next(); i++) { // There should be three times as many incoming keys if (inKeys.size() < (i + 1) * 3) throw new DbStateException(); - KeySetId keySetId = new KeySetId(rs.getInt(1)); + TransportKeySetId keySetId = + new TransportKeySetId(rs.getInt(1)); ContactId contactId = new ContactId(rs.getInt(2)); - long rotationPeriod = rs.getLong(3); + long timePeriod = rs.getLong(3); SecretKey tagKey = new SecretKey(rs.getBytes(4)); SecretKey headerKey = new SecretKey(rs.getBytes(5)); long streamCounter = rs.getLong(6); boolean active = rs.getBoolean(7); OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey, - rotationPeriod, streamCounter, active); + timePeriod, streamCounter, active); IncomingKeys inPrev = inKeys.get(i * 3); IncomingKeys inCurr = inKeys.get(i * 3 + 1); IncomingKeys inNext = inKeys.get(i * 3 + 2); TransportKeys transportKeys = new TransportKeys(t, inPrev, inCurr, inNext, outCurr); - keys.add(new KeySet(keySetId, contactId, transportKeys)); + keys.add(new TransportKeySet(keySetId, contactId, + transportKeys)); } rs.close(); ps.close(); @@ -2216,7 +2531,26 @@ abstract class JdbcDatabase implements Database { @Override public void incrementStreamCounter(Connection txn, TransportId t, - KeySetId k) throws DbException { + HandshakeKeySetId k) throws DbException { + PreparedStatement ps = null; + try { + String sql = "UPDATE outgoingHandshakeKeys SET stream = stream + 1" + + " WHERE transportId = ? AND keySetId = ?"; + ps = txn.prepareStatement(sql); + ps.setString(1, t.getString()); + ps.setInt(2, k.getInt()); + int affected = ps.executeUpdate(); + if (affected != 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + + @Override + public void incrementStreamCounter(Connection txn, TransportId t, + TransportKeySetId k) throws DbException { PreparedStatement ps = null; try { String sql = "UPDATE outgoingKeys SET stream = stream + 1" @@ -2594,6 +2928,27 @@ abstract class JdbcDatabase implements Database { } } + @Override + public void removeHandshakeKeys(Connection txn, TransportId t, + HandshakeKeySetId k) throws DbException { + PreparedStatement ps = null; + try { + // Delete any existing outgoing keys - this will also remove any + // incoming keys with the same key set ID + String sql = "DELETE FROM outgoingHandshakeKeys" + + " WHERE transportId = ? AND keySetId = ?"; + ps = txn.prepareStatement(sql); + ps.setString(1, t.getString()); + ps.setInt(2, k.getInt()); + int affected = ps.executeUpdate(); + if (affected < 0) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public void removeLocalAuthor(Connection txn, AuthorId a) throws DbException { @@ -2671,6 +3026,24 @@ abstract class JdbcDatabase implements Database { } } + @Override + public void removePendingContact(Connection txn, PendingContactId p) + throws DbException { + PreparedStatement ps = null; + try { + String sql = "DELETE FROM pendingContacts" + + " WHERE pendingContactId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, p.getBytes()); + int affected = ps.executeUpdate(); + if (affected != 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public void removeTransport(Connection txn, TransportId t) throws DbException { @@ -2689,8 +3062,8 @@ abstract class JdbcDatabase implements Database { } @Override - public void removeTransportKeys(Connection txn, TransportId t, KeySetId k) - throws DbException { + public void removeTransportKeys(Connection txn, TransportId t, + TransportKeySetId k) throws DbException { PreparedStatement ps = null; try { // Delete any existing outgoing keys - this will also remove any @@ -2893,19 +3266,63 @@ abstract class JdbcDatabase implements Database { } @Override - public void setReorderingWindow(Connection txn, KeySetId k, TransportId t, - long rotationPeriod, long base, byte[] bitmap) throws DbException { + public void setPendingContactState(Connection txn, PendingContactId p, + PendingContactState state) throws DbException { + PreparedStatement ps = null; + try { + String sql = "UPDATE pendingContacts SET state = ?" + + " WHERE pendingContactId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, state.getValue()); + ps.setBytes(2, p.getBytes()); + int affected = ps.executeUpdate(); + if (affected != 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + + @Override + public void setReorderingWindow(Connection txn, TransportKeySetId k, + TransportId t, long timePeriod, long base, byte[] bitmap) + throws DbException { PreparedStatement ps = null; try { String sql = "UPDATE incomingKeys SET base = ?, bitmap = ?" + " WHERE transportId = ? AND keySetId = ?" - + " AND rotationPeriod = ?"; + + " AND timePeriod = ?"; ps = txn.prepareStatement(sql); ps.setLong(1, base); ps.setBytes(2, bitmap); ps.setString(3, t.getString()); ps.setInt(4, k.getInt()); - ps.setLong(5, rotationPeriod); + ps.setLong(5, timePeriod); + int affected = ps.executeUpdate(); + if (affected < 0 || affected > 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + + @Override + public void setReorderingWindow(Connection txn, HandshakeKeySetId k, + TransportId t, long timePeriod, long base, byte[] bitmap) + throws DbException { + PreparedStatement ps = null; + try { + String sql = "UPDATE incomingHandshakeKeys SET base = ?, bitmap = ?" + + " WHERE transportId = ? AND keySetId = ?" + + " AND timePeriod = ?"; + ps = txn.prepareStatement(sql); + ps.setLong(1, base); + ps.setBytes(2, bitmap); + ps.setString(3, t.getString()); + ps.setInt(4, k.getInt()); + ps.setLong(5, timePeriod); int affected = ps.executeUpdate(); if (affected < 0 || affected > 1) throw new DbStateException(); ps.close(); @@ -2917,7 +3334,7 @@ abstract class JdbcDatabase implements Database { @Override public void setTransportKeysActive(Connection txn, TransportId t, - KeySetId k) throws DbException { + TransportKeySetId k) throws DbException { PreparedStatement ps = null; try { String sql = "UPDATE outgoingKeys SET active = true" @@ -2972,18 +3389,18 @@ abstract class JdbcDatabase implements Database { } @Override - public void updateTransportKeys(Connection txn, KeySet ks) + public void updateTransportKeys(Connection txn, TransportKeySet ks) throws DbException { PreparedStatement ps = null; try { // Update the outgoing keys - String sql = "UPDATE outgoingKeys SET rotationPeriod = ?," + String sql = "UPDATE outgoingKeys SET timePeriod = ?," + " tagKey = ?, headerKey = ?, stream = ?" + " WHERE transportId = ? AND keySetId = ?"; ps = txn.prepareStatement(sql); - TransportKeys k = ks.getTransportKeys(); + TransportKeys k = ks.getKeys(); OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); - ps.setLong(1, outCurr.getRotationPeriod()); + ps.setLong(1, outCurr.getTimePeriod()); ps.setBytes(2, outCurr.getTagKey().getBytes()); ps.setBytes(3, outCurr.getHeaderKey().getBytes()); ps.setLong(4, outCurr.getStreamCounter()); @@ -2993,34 +3410,101 @@ abstract class JdbcDatabase implements Database { if (affected < 0 || affected > 1) throw new DbStateException(); ps.close(); // Update the incoming keys - sql = "UPDATE incomingKeys SET rotationPeriod = ?," + sql = "UPDATE incomingKeys SET timePeriod = ?," + " tagKey = ?, headerKey = ?, base = ?, bitmap = ?" + " WHERE transportId = ? AND keySetId = ?" + " AND periodOffset = ?"; ps = txn.prepareStatement(sql); ps.setString(6, k.getTransportId().getString()); ps.setInt(7, ks.getKeySetId().getInt()); - // Previous rotation period + // Previous time period IncomingKeys inPrev = k.getPreviousIncomingKeys(); - ps.setLong(1, inPrev.getRotationPeriod()); + ps.setLong(1, inPrev.getTimePeriod()); ps.setBytes(2, inPrev.getTagKey().getBytes()); ps.setBytes(3, inPrev.getHeaderKey().getBytes()); ps.setLong(4, inPrev.getWindowBase()); ps.setBytes(5, inPrev.getWindowBitmap()); ps.setInt(8, OFFSET_PREV); ps.addBatch(); - // Current rotation period + // Current time period IncomingKeys inCurr = k.getCurrentIncomingKeys(); - ps.setLong(1, inCurr.getRotationPeriod()); + ps.setLong(1, inCurr.getTimePeriod()); ps.setBytes(2, inCurr.getTagKey().getBytes()); ps.setBytes(3, inCurr.getHeaderKey().getBytes()); ps.setLong(4, inCurr.getWindowBase()); ps.setBytes(5, inCurr.getWindowBitmap()); ps.setInt(8, OFFSET_CURR); ps.addBatch(); - // Next rotation period + // Next time period IncomingKeys inNext = k.getNextIncomingKeys(); - ps.setLong(1, inNext.getRotationPeriod()); + ps.setLong(1, inNext.getTimePeriod()); + ps.setBytes(2, inNext.getTagKey().getBytes()); + ps.setBytes(3, inNext.getHeaderKey().getBytes()); + ps.setLong(4, inNext.getWindowBase()); + ps.setBytes(5, inNext.getWindowBitmap()); + ps.setInt(8, OFFSET_NEXT); + ps.addBatch(); + int[] batchAffected = ps.executeBatch(); + if (batchAffected.length != 3) throw new DbStateException(); + for (int rows : batchAffected) + if (rows < 0 || rows > 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + + @Override + public void updateHandshakeKeys(Connection txn, HandshakeKeySet ks) + throws DbException { + PreparedStatement ps = null; + try { + // Update the outgoing keys + String sql = "UPDATE outgoingHandshakeKeys SET timePeriod = ?," + + " tagKey = ?, headerKey = ?, stream = ?" + + " WHERE transportId = ? AND keySetId = ?"; + ps = txn.prepareStatement(sql); + HandshakeKeys k = ks.getKeys(); + OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); + ps.setLong(1, outCurr.getTimePeriod()); + ps.setBytes(2, outCurr.getTagKey().getBytes()); + ps.setBytes(3, outCurr.getHeaderKey().getBytes()); + ps.setLong(4, outCurr.getStreamCounter()); + ps.setString(5, k.getTransportId().getString()); + ps.setInt(6, ks.getKeySetId().getInt()); + int affected = ps.executeUpdate(); + if (affected < 0 || affected > 1) throw new DbStateException(); + ps.close(); + // Update the incoming keys + sql = "UPDATE incomingHandshakeKeys SET timePeriod = ?," + + " tagKey = ?, headerKey = ?, base = ?, bitmap = ?" + + " WHERE transportId = ? AND keySetId = ?" + + " AND periodOffset = ?"; + ps = txn.prepareStatement(sql); + ps.setString(6, k.getTransportId().getString()); + ps.setInt(7, ks.getKeySetId().getInt()); + // Previous time period + IncomingKeys inPrev = k.getPreviousIncomingKeys(); + ps.setLong(1, inPrev.getTimePeriod()); + ps.setBytes(2, inPrev.getTagKey().getBytes()); + ps.setBytes(3, inPrev.getHeaderKey().getBytes()); + ps.setLong(4, inPrev.getWindowBase()); + ps.setBytes(5, inPrev.getWindowBitmap()); + ps.setInt(8, OFFSET_PREV); + ps.addBatch(); + // Current time period + IncomingKeys inCurr = k.getCurrentIncomingKeys(); + ps.setLong(1, inCurr.getTimePeriod()); + ps.setBytes(2, inCurr.getTagKey().getBytes()); + ps.setBytes(3, inCurr.getHeaderKey().getBytes()); + ps.setLong(4, inCurr.getWindowBase()); + ps.setBytes(5, inCurr.getWindowBitmap()); + ps.setInt(8, OFFSET_CURR); + ps.addBatch(); + // Next time period + IncomingKeys inNext = k.getNextIncomingKeys(); + ps.setLong(1, inNext.getTimePeriod()); ps.setBytes(2, inNext.getTagKey().getBytes()); ps.setBytes(3, inNext.getHeaderKey().getBytes()); ps.setLong(4, inNext.getWindowBase()); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Migration40_41.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Migration40_41.java index 709be524d..8c7f9bcf6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Migration40_41.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Migration40_41.java @@ -17,7 +17,7 @@ class Migration40_41 implements Migration { private final DatabaseTypes dbTypes; - public Migration40_41(DatabaseTypes databaseTypes) { + Migration40_41(DatabaseTypes databaseTypes) { this.dbTypes = databaseTypes; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Migration41_42.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Migration41_42.java new file mode 100644 index 000000000..9a9f01633 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Migration41_42.java @@ -0,0 +1,97 @@ +package org.briarproject.bramble.db; + +import org.briarproject.bramble.api.db.DbException; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.logging.Logger; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.db.JdbcUtils.tryToClose; + +class Migration41_42 implements Migration { + + private static final Logger LOG = getLogger(Migration41_42.class.getName()); + + private final DatabaseTypes dbTypes; + + Migration41_42(DatabaseTypes dbTypes) { + this.dbTypes = dbTypes; + } + + @Override + public int getStartVersion() { + return 41; + } + + @Override + public int getEndVersion() { + return 42; + } + + @Override + public void migrate(Connection txn) throws DbException { + Statement s = null; + try { + s = txn.createStatement(); + s.execute("ALTER TABLE outgoingKeys" + + " ALTER COLUMN rotationPeriod" + + " RENAME TO timePeriod"); + s.execute("ALTER TABLE incomingKeys" + + " ALTER COLUMN rotationPeriod" + + " RENAME TO timePeriod"); + s.execute("ALTER TABLE incomingKeys" + + " DROP COLUMN contactId"); + s.execute(dbTypes.replaceTypes("CREATE TABLE pendingContacts" + + " (pendingContactId _HASH NOT NULL," + + " publicKey _BINARY NOT NULL," + + " alias _STRING NOT NULL," + + " state INT NOT NULL," + + " timestamp BIGINT NOT NULL," + + " PRIMARY KEY (pendingContactId))")); + s.execute(dbTypes.replaceTypes("CREATE TABLE outgoingHandshakeKeys" + + " (transportId _STRING NOT NULL," + + " keySetId _COUNTER," + + " timePeriod BIGINT NOT NULL," + + " contactId INT," // Null if contact is pending + + " pendingContactId _HASH," // Null if not pending + + " rootKey _SECRET NOT NULL," + + " alice BOOLEAN NOT NULL," + + " tagKey _SECRET NOT NULL," + + " headerKey _SECRET NOT NULL," + + " stream BIGINT NOT NULL," + + " PRIMARY KEY (transportId, keySetId)," + + " FOREIGN KEY (transportId)" + + " REFERENCES transports (transportId)" + + " ON DELETE CASCADE," + + " UNIQUE (keySetId)," + + " FOREIGN KEY (contactId)" + + " REFERENCES contacts (contactId)" + + " ON DELETE CASCADE," + + " FOREIGN KEY (pendingContactId)" + + " REFERENCES pendingContacts (pendingContactId)" + + " ON DELETE CASCADE)")); + s.execute(dbTypes.replaceTypes("CREATE TABLE incomingHandshakeKeys" + + " (transportId _STRING NOT NULL," + + " keySetId INT NOT NULL," + + " timePeriod BIGINT NOT NULL," + + " tagKey _SECRET NOT NULL," + + " headerKey _SECRET NOT NULL," + + " base BIGINT NOT NULL," + + " bitmap _BINARY NOT NULL," + + " periodOffset INT NOT NULL," + + " PRIMARY KEY (transportId, keySetId, periodOffset)," + + " FOREIGN KEY (transportId)" + + " REFERENCES transports (transportId)" + + " ON DELETE CASCADE," + + " FOREIGN KEY (keySetId)" + + " REFERENCES outgoingHandshakeKeys (keySetId)" + + " ON DELETE CASCADE)")); + } catch (SQLException e) { + tryToClose(s, LOG, WARNING); + throw new DbException(e); + } + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocol.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocol.java index 5825c9500..682041c90 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocol.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocol.java @@ -14,7 +14,7 @@ import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Arrays; -import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL; +import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_KEY_LABEL; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL; @@ -90,7 +90,7 @@ class KeyAgreementProtocol { /** * Perform the BQP protocol. * - * @return the negotiated master secret. + * @return the negotiated master key. * @throws AbortException when the protocol may have been tampered with. * @throws IOException for all other other connection errors. */ @@ -115,7 +115,7 @@ class KeyAgreementProtocol { receiveConfirm(s, theirPublicKey); sendConfirm(s, theirPublicKey); } - return crypto.deriveKey(MASTER_SECRET_LABEL, s); + return crypto.deriveKey(MASTER_KEY_LABEL, s); } catch (AbortException e) { sendAbort(e.getCause() != null); throw e; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java index b6e994231..e2735c394 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementTaskImpl.java @@ -114,9 +114,9 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask, keyAgreementCrypto, payloadEncoder, transport, remotePayload, localPayload, localKeyPair, alice); try { - SecretKey master = protocol.perform(); + SecretKey masterKey = protocol.perform(); KeyAgreementResult result = - new KeyAgreementResult(master, transport.getConnection(), + new KeyAgreementResult(masterKey, transport.getConnection(), transport.getTransportId(), alice); LOG.info("Finished BQP protocol"); // Broadcast result to caller diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java index 624c8b2b8..8e34a78dd 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java @@ -19,8 +19,8 @@ import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.transport.KeyManager; -import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.StreamContext; +import org.briarproject.bramble.api.transport.TransportKeySetId; import java.util.HashMap; import java.util.Map; @@ -95,22 +95,22 @@ class KeyManagerImpl implements KeyManager, Service, EventListener { } @Override - public Map addContact(Transaction txn, ContactId c, - SecretKey master, long timestamp, boolean alice, boolean active) - throws DbException { - Map ids = new HashMap<>(); + public Map addContact(Transaction txn, + ContactId c, SecretKey rootKey, long timestamp, boolean alice, + boolean active) throws DbException { + Map ids = new HashMap<>(); for (Entry e : managers.entrySet()) { TransportId t = e.getKey(); TransportKeyManager m = e.getValue(); - ids.put(t, m.addContact(txn, c, master, timestamp, alice, active)); + ids.put(t, m.addContact(txn, c, rootKey, timestamp, alice, active)); } return ids; } @Override - public void activateKeys(Transaction txn, Map keys) - throws DbException { - for (Entry e : keys.entrySet()) { + public void activateKeys(Transaction txn, Map keys) throws DbException { + for (Entry e : keys.entrySet()) { TransportId t = e.getKey(); TransportKeyManager m = managers.get(t); if (m == null) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableIncomingKeys.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableIncomingKeys.java index 3f2f94a5c..994013044 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableIncomingKeys.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableIncomingKeys.java @@ -11,18 +11,18 @@ import javax.annotation.concurrent.NotThreadSafe; class MutableIncomingKeys { private final SecretKey tagKey, headerKey; - private final long rotationPeriod; + private final long timePeriod; private final ReorderingWindow window; MutableIncomingKeys(IncomingKeys in) { tagKey = in.getTagKey(); headerKey = in.getHeaderKey(); - rotationPeriod = in.getRotationPeriod(); + timePeriod = in.getTimePeriod(); window = new ReorderingWindow(in.getWindowBase(), in.getWindowBitmap()); } IncomingKeys snapshot() { - return new IncomingKeys(tagKey, headerKey, rotationPeriod, + return new IncomingKeys(tagKey, headerKey, timePeriod, window.getBase(), window.getBitmap()); } @@ -34,8 +34,8 @@ class MutableIncomingKeys { return headerKey; } - long getRotationPeriod() { - return rotationPeriod; + long getTimePeriod() { + return timePeriod; } ReorderingWindow getWindow() { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableKeySet.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableKeySet.java index 63ce3134f..5c24b2220 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableKeySet.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableKeySet.java @@ -1,22 +1,22 @@ package org.briarproject.bramble.transport; import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.transport.TransportKeySetId; class MutableKeySet { - private final KeySetId keySetId; + private final TransportKeySetId keySetId; private final ContactId contactId; private final MutableTransportKeys transportKeys; - MutableKeySet(KeySetId keySetId, ContactId contactId, + MutableKeySet(TransportKeySetId keySetId, ContactId contactId, MutableTransportKeys transportKeys) { this.keySetId = keySetId; this.contactId = contactId; this.transportKeys = transportKeys; } - KeySetId getKeySetId() { + TransportKeySetId getKeySetId() { return keySetId; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableOutgoingKeys.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableOutgoingKeys.java index c195f445c..44ba3a3ea 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableOutgoingKeys.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/MutableOutgoingKeys.java @@ -11,20 +11,20 @@ import javax.annotation.concurrent.NotThreadSafe; class MutableOutgoingKeys { private final SecretKey tagKey, headerKey; - private final long rotationPeriod; + private final long timePeriod; private long streamCounter; private boolean active; MutableOutgoingKeys(OutgoingKeys out) { tagKey = out.getTagKey(); headerKey = out.getHeaderKey(); - rotationPeriod = out.getRotationPeriod(); + timePeriod = out.getTimePeriod(); streamCounter = out.getStreamCounter(); active = out.isActive(); } OutgoingKeys snapshot() { - return new OutgoingKeys(tagKey, headerKey, rotationPeriod, + return new OutgoingKeys(tagKey, headerKey, timePeriod, streamCounter, active); } @@ -36,8 +36,8 @@ class MutableOutgoingKeys { return headerKey; } - long getRotationPeriod() { - return rotationPeriod; + long getTimePeriod() { + return timePeriod; } long getStreamCounter() { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java index b407db16c..2d08ec4c1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java @@ -5,8 +5,8 @@ import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.StreamContext; +import org.briarproject.bramble.api.transport.TransportKeySetId; import javax.annotation.Nullable; @@ -15,10 +15,11 @@ interface TransportKeyManager { void start(Transaction txn) throws DbException; - KeySetId addContact(Transaction txn, ContactId c, SecretKey master, - long timestamp, boolean alice, boolean active) throws DbException; + TransportKeySetId addContact(Transaction txn, ContactId c, + SecretKey rootKey, long timestamp, boolean alice, boolean active) + throws DbException; - void activateKeys(Transaction txn, KeySetId k) throws DbException; + void activateKeys(Transaction txn, TransportKeySetId k) throws DbException; void removeContact(ContactId c); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java index ed51c3919..e93aab417 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java @@ -11,9 +11,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Scheduler; -import org.briarproject.bramble.api.transport.KeySet; -import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.StreamContext; +import org.briarproject.bramble.api.transport.TransportKeySet; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.transport.ReorderingWindow.Change; @@ -51,12 +51,12 @@ class TransportKeyManagerImpl implements TransportKeyManager { private final ScheduledExecutorService scheduler; private final Clock clock; private final TransportId transportId; - private final long rotationPeriodLength; + private final long timePeriodLength; private final AtomicBoolean used = new AtomicBoolean(false); private final ReentrantLock lock = new ReentrantLock(); // The following are locking: lock - private final Map keys = new HashMap<>(); + private final Map keys = new HashMap<>(); private final Map inContexts = new HashMap<>(); private final Map outContexts = new HashMap<>(); @@ -70,7 +70,7 @@ class TransportKeyManagerImpl implements TransportKeyManager { this.scheduler = scheduler; this.clock = clock; this.transportId = transportId; - rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; + timePeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; } @Override @@ -80,8 +80,9 @@ class TransportKeyManagerImpl implements TransportKeyManager { lock.lock(); try { // Load the transport keys from the DB - Collection loaded = db.getTransportKeys(txn, transportId); - // Rotate the keys to the current rotation period + Collection loaded = + db.getTransportKeys(txn, transportId); + // Rotate the keys to the current time period RotationResult rotationResult = rotateKeys(loaded, now); // Initialise mutable state for all contacts addKeys(rotationResult.current); @@ -95,15 +96,17 @@ class TransportKeyManagerImpl implements TransportKeyManager { scheduleKeyRotation(now); } - private RotationResult rotateKeys(Collection keys, long now) { + private RotationResult rotateKeys(Collection keys, + long now) { RotationResult rotationResult = new RotationResult(); - long rotationPeriod = now / rotationPeriodLength; - for (KeySet ks : keys) { - TransportKeys k = ks.getTransportKeys(); - TransportKeys k1 = - transportCrypto.rotateTransportKeys(k, rotationPeriod); - KeySet ks1 = new KeySet(ks.getKeySetId(), ks.getContactId(), k1); - if (k1.getRotationPeriod() > k.getRotationPeriod()) + long timePeriod = now / timePeriodLength; + for (TransportKeySet ks : keys) { + TransportKeys k = ks.getKeys(); + TransportKeys k1 = transportCrypto.rotateTransportKeys(k, + timePeriod); + TransportKeySet ks1 = new TransportKeySet(ks.getKeySetId(), + ks.getContactId(), k1); + if (k1.getTimePeriod() > k.getTimePeriod()) rotationResult.rotated.add(ks1); rotationResult.current.add(ks1); } @@ -111,15 +114,15 @@ class TransportKeyManagerImpl implements TransportKeyManager { } // Locking: lock - private void addKeys(Collection keys) { - for (KeySet ks : keys) { + private void addKeys(Collection keys) { + for (TransportKeySet ks : keys) { addKeys(ks.getKeySetId(), ks.getContactId(), - new MutableTransportKeys(ks.getTransportKeys())); + new MutableTransportKeys(ks.getKeys())); } } // Locking: lock - private void addKeys(KeySetId keySetId, ContactId contactId, + private void addKeys(TransportKeySetId keySetId, ContactId contactId, MutableTransportKeys m) { MutableKeySet ks = new MutableKeySet(keySetId, contactId, m); keys.put(keySetId, ks); @@ -130,7 +133,7 @@ class TransportKeyManagerImpl implements TransportKeyManager { } // Locking: lock - private void encodeTags(KeySetId keySetId, ContactId contactId, + private void encodeTags(TransportKeySetId keySetId, ContactId contactId, MutableIncomingKeys inKeys) { for (long streamNumber : inKeys.getWindow().getUnseen()) { TagContext tagCtx = @@ -155,7 +158,7 @@ class TransportKeyManagerImpl implements TransportKeyManager { } private void scheduleKeyRotation(long now) { - long delay = rotationPeriodLength - now % rotationPeriodLength; + long delay = timePeriodLength - now % timePeriodLength; scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS); } @@ -170,20 +173,21 @@ class TransportKeyManagerImpl implements TransportKeyManager { } @Override - public KeySetId addContact(Transaction txn, ContactId c, SecretKey master, - long timestamp, boolean alice, boolean active) throws DbException { + public TransportKeySetId addContact(Transaction txn, ContactId c, + SecretKey rootKey, long timestamp, boolean alice, boolean active) + throws DbException { lock.lock(); try { - // Work out what rotation period the timestamp belongs to - long rotationPeriod = timestamp / rotationPeriodLength; + // Work out what time period the timestamp belongs to + long timePeriod = timestamp / timePeriodLength; // Derive the transport keys TransportKeys k = transportCrypto.deriveTransportKeys(transportId, - master, rotationPeriod, alice, active); - // Rotate the keys to the current rotation period if necessary - rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength; - k = transportCrypto.rotateTransportKeys(k, rotationPeriod); + rootKey, timePeriod, alice, active); + // Rotate the keys to the current time period if necessary + timePeriod = clock.currentTimeMillis() / timePeriodLength; + k = transportCrypto.rotateTransportKeys(k, timePeriod); // Write the keys back to the DB - KeySetId keySetId = db.addTransportKeys(txn, c, k); + TransportKeySetId keySetId = db.addTransportKeys(txn, c, k); // Initialise mutable state for the contact addKeys(keySetId, c, new MutableTransportKeys(k)); return keySetId; @@ -193,7 +197,8 @@ class TransportKeyManagerImpl implements TransportKeyManager { } @Override - public void activateKeys(Transaction txn, KeySetId k) throws DbException { + public void activateKeys(Transaction txn, TransportKeySetId k) + throws DbException { lock.lock(); try { MutableKeySet ks = keys.get(k); @@ -300,7 +305,7 @@ class TransportKeyManagerImpl implements TransportKeyManager { } // Write the window back to the DB db.setReorderingWindow(txn, tagCtx.keySetId, transportId, - inKeys.getRotationPeriod(), window.getBase(), + inKeys.getTimePeriod(), window.getBase(), window.getBitmap()); // If the outgoing keys are inactive, activate them MutableKeySet ks = keys.get(tagCtx.keySetId); @@ -322,11 +327,11 @@ class TransportKeyManagerImpl implements TransportKeyManager { long now = clock.currentTimeMillis(); lock.lock(); try { - // Rotate the keys to the current rotation period - Collection snapshot = new ArrayList<>(keys.size()); + // Rotate the keys to the current time period + Collection snapshot = new ArrayList<>(keys.size()); for (MutableKeySet ks : keys.values()) { - snapshot.add(new KeySet(ks.getKeySetId(), ks.getContactId(), - ks.getTransportKeys().snapshot())); + snapshot.add(new TransportKeySet(ks.getKeySetId(), + ks.getContactId(), ks.getTransportKeys().snapshot())); } RotationResult rotationResult = rotateKeys(snapshot, now); // Rebuild the mutable state for all contacts @@ -346,12 +351,12 @@ class TransportKeyManagerImpl implements TransportKeyManager { private static class TagContext { - private final KeySetId keySetId; + private final TransportKeySetId keySetId; private final ContactId contactId; private final MutableIncomingKeys inKeys; private final long streamNumber; - private TagContext(KeySetId keySetId, ContactId contactId, + private TagContext(TransportKeySetId keySetId, ContactId contactId, MutableIncomingKeys inKeys, long streamNumber) { this.keySetId = keySetId; this.contactId = contactId; @@ -362,7 +367,7 @@ class TransportKeyManagerImpl implements TransportKeyManager { private static class RotationResult { - private final Collection current = new ArrayList<>(); - private final Collection rotated = new ArrayList<>(); + private final Collection current = new ArrayList<>(); + private final Collection rotated = new ArrayList<>(); } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java index 375fcade5..9289bf4e8 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java @@ -64,7 +64,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase { @Test public void testAddContact() throws Exception { - SecretKey master = getSecretKey(); + SecretKey rootKey = getSecretKey(); long timestamp = System.currentTimeMillis(); boolean alice = new Random().nextBoolean(); Transaction txn = new Transaction(null, false); @@ -73,14 +73,14 @@ public class ContactManagerImplTest extends BrambleMockTestCase { oneOf(db).transactionWithResult(with(false), withDbCallable(txn)); oneOf(db).addContact(txn, remote, local, verified, active); will(returnValue(contactId)); - oneOf(keyManager).addContact(txn, contactId, master, timestamp, + oneOf(keyManager).addContact(txn, contactId, rootKey, timestamp, alice, active); oneOf(db).getContact(txn, contactId); will(returnValue(contact)); }}); assertEquals(contactId, contactManager.addContact(remote, local, - master, timestamp, alice, verified, active)); + rootKey, timestamp, alice, verified, active)); } @Test diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/HandshakeKeyDerivationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/HandshakeKeyDerivationTest.java new file mode 100644 index 000000000..e741b9f08 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/HandshakeKeyDerivationTest.java @@ -0,0 +1,167 @@ +package org.briarproject.bramble.crypto; + +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.crypto.TransportCrypto; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.transport.HandshakeKeys; +import org.briarproject.bramble.test.BrambleTestCase; +import org.briarproject.bramble.test.TestSecureRandomProvider; +import org.junit.Test; + +import java.util.Arrays; + +import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertAllDifferent; +import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertMatches; +import static org.briarproject.bramble.test.TestUtils.getSecretKey; +import static org.briarproject.bramble.test.TestUtils.getTransportId; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertSame; + +public class HandshakeKeyDerivationTest extends BrambleTestCase { + + private final CryptoComponent crypto = + new CryptoComponentImpl(new TestSecureRandomProvider(), null); + private final TransportCrypto transportCrypto = + new TransportCryptoImpl(crypto); + private final TransportId transportId = getTransportId(); + private final SecretKey rootKey = getSecretKey(); + + @Test + public void testKeysAreDistinct() { + HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, false); + assertAllDifferent(kA); + assertAllDifferent(kB); + } + + @Test + public void testKeysAreNotUpdatedToPreviousPeriod() { + HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 122); + assertSame(k, k1); + } + + @Test + public void testKeysAreNotUpdatedToCurrentPeriod() { + HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 123); + assertSame(k, k1); + } + + @Test + public void testKeysAreUpdatedByOnePeriod() { + HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 124); + assertSame(k.getCurrentIncomingKeys(), k1.getPreviousIncomingKeys()); + assertSame(k.getNextIncomingKeys(), k1.getCurrentIncomingKeys()); + } + + @Test + public void testKeysAreUpdatedByTwoPeriods() { + HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 125); + assertSame(k.getNextIncomingKeys(), k1.getPreviousIncomingKeys()); + } + + @Test + public void testKeysAreUpdatedByThreePeriods() { + HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys k1 = transportCrypto.updateHandshakeKeys(k, 126); + assertAllDifferent(k, k1); + } + + @Test + public void testCurrentKeysMatchContact() { + // Start in time period 123 + HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, false); + // Alice's incoming keys should equal Bob's outgoing keys + assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys()); + // Bob's incoming keys should equal Alice's outgoing keys + assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys()); + // Update into the future + kA = transportCrypto.updateHandshakeKeys(kA, 456); + kB = transportCrypto.updateHandshakeKeys(kB, 456); + // Alice's incoming keys should equal Bob's outgoing keys + assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys()); + // Bob's incoming keys should equal Alice's outgoing keys + assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys()); + } + + @Test + public void testPreviousKeysMatchContact() { + // Start in time period 123 + HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, false); + // Compare Alice's previous keys in period 456 with Bob's current keys + // in period 455 + kA = transportCrypto.updateHandshakeKeys(kA, 456); + kB = transportCrypto.updateHandshakeKeys(kB, 455); + // Alice's previous incoming keys should equal Bob's current + // outgoing keys + assertMatches(kA.getPreviousIncomingKeys(), + kB.getCurrentOutgoingKeys()); + // Compare Alice's current keys in period 456 with Bob's previous keys + // in period 457 + kB = transportCrypto.updateHandshakeKeys(kB, 457); + // Bob's previous incoming keys should equal Alice's current + // outgoing keys + assertMatches(kB.getPreviousIncomingKeys(), + kA.getCurrentOutgoingKeys()); + } + + @Test + public void testNextKeysMatchContact() { + // Start in time period 123 + HandshakeKeys kA = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys kB = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, false); + // Compare Alice's current keys in period 456 with Bob's next keys in + // period 455 + kA = transportCrypto.updateHandshakeKeys(kA, 456); + kB = transportCrypto.updateHandshakeKeys(kB, 455); + // Bob's next incoming keys should equal Alice's current outgoing keys + assertMatches(kB.getNextIncomingKeys(), kA.getCurrentOutgoingKeys()); + // Compare Alice's next keys in period 456 with Bob's current keys + // in period 457 + kB = transportCrypto.updateHandshakeKeys(kB, 457); + // Alice's next incoming keys should equal Bob's current outgoing keys + assertMatches(kA.getNextIncomingKeys(), kB.getCurrentOutgoingKeys()); + } + + @Test + public void testRootKeyAffectsOutput() { + SecretKey rootKey1 = getSecretKey(); + assertFalse(Arrays.equals(rootKey.getBytes(), rootKey1.getBytes())); + HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys k1 = transportCrypto.deriveHandshakeKeys(transportId, + rootKey1, 123, true); + assertAllDifferent(k, k1); + } + + @Test + public void testTransportIdAffectsOutput() { + TransportId transportId1 = getTransportId(); + assertNotEquals(transportId.getString(), transportId1.getString()); + HandshakeKeys k = transportCrypto.deriveHandshakeKeys(transportId, + rootKey, 123, true); + HandshakeKeys k1 = transportCrypto.deriveHandshakeKeys(transportId1, + rootKey, 123, true); + assertAllDifferent(k, k1); + } +} \ No newline at end of file diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java deleted file mode 100644 index 969d62588..000000000 --- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTest.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.briarproject.bramble.crypto; - -import org.briarproject.bramble.api.Bytes; -import org.briarproject.bramble.api.crypto.CryptoComponent; -import org.briarproject.bramble.api.crypto.SecretKey; -import org.briarproject.bramble.api.crypto.TransportCrypto; -import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.transport.TransportKeys; -import org.briarproject.bramble.test.BrambleTestCase; -import org.briarproject.bramble.test.TestSecureRandomProvider; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.briarproject.bramble.test.TestUtils.getSecretKey; -import static org.briarproject.bramble.test.TestUtils.getTransportId; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class KeyDerivationTest extends BrambleTestCase { - - private final CryptoComponent crypto = - new CryptoComponentImpl(new TestSecureRandomProvider(), null); - private final TransportCrypto transportCrypto = - new TransportCryptoImpl(crypto); - private final TransportId transportId = getTransportId(); - private final SecretKey master = getSecretKey(); - - @Test - public void testKeysAreDistinct() { - TransportKeys k = transportCrypto.deriveTransportKeys(transportId, - master, 123, true, true); - assertAllDifferent(k); - } - - @Test - public void testCurrentKeysMatchCurrentKeysOfContact() { - // Start in rotation period 123 - TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, - master, 123, true, true); - TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, - master, 123, false, true); - // Alice's incoming keys should equal Bob's outgoing keys - assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(), - kB.getCurrentOutgoingKeys().getTagKey().getBytes()); - assertArrayEquals(kA.getCurrentIncomingKeys().getHeaderKey().getBytes(), - kB.getCurrentOutgoingKeys().getHeaderKey().getBytes()); - // Alice's outgoing keys should equal Bob's incoming keys - assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(), - kB.getCurrentIncomingKeys().getTagKey().getBytes()); - assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(), - kB.getCurrentIncomingKeys().getHeaderKey().getBytes()); - // Rotate into the future - kA = transportCrypto.rotateTransportKeys(kA, 456); - kB = transportCrypto.rotateTransportKeys(kB, 456); - // Alice's incoming keys should equal Bob's outgoing keys - assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(), - kB.getCurrentOutgoingKeys().getTagKey().getBytes()); - assertArrayEquals(kA.getCurrentIncomingKeys().getHeaderKey().getBytes(), - kB.getCurrentOutgoingKeys().getHeaderKey().getBytes()); - // Alice's outgoing keys should equal Bob's incoming keys - assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(), - kB.getCurrentIncomingKeys().getTagKey().getBytes()); - assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(), - kB.getCurrentIncomingKeys().getHeaderKey().getBytes()); - } - - @Test - public void testPreviousKeysMatchPreviousKeysOfContact() { - // Start in rotation period 123 - TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, - master, 123, true, true); - TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, - master, 123, false, true); - // Compare Alice's previous keys in period 456 with Bob's current keys - // in period 455 - kA = transportCrypto.rotateTransportKeys(kA, 456); - kB = transportCrypto.rotateTransportKeys(kB, 455); - // Alice's previous incoming keys should equal Bob's outgoing keys - assertArrayEquals(kA.getPreviousIncomingKeys().getTagKey().getBytes(), - kB.getCurrentOutgoingKeys().getTagKey().getBytes()); - assertArrayEquals( - kA.getPreviousIncomingKeys().getHeaderKey().getBytes(), - kB.getCurrentOutgoingKeys().getHeaderKey().getBytes()); - // Compare Alice's current keys in period 456 with Bob's previous keys - // in period 457 - kB = transportCrypto.rotateTransportKeys(kB, 457); - // Alice's outgoing keys should equal Bob's previous incoming keys - assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(), - kB.getPreviousIncomingKeys().getTagKey().getBytes()); - assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(), - kB.getPreviousIncomingKeys().getHeaderKey().getBytes()); - } - - @Test - public void testNextKeysMatchNextKeysOfContact() { - // Start in rotation period 123 - TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, - master, 123, true, true); - TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, - master, 123, false, true); - // Compare Alice's current keys in period 456 with Bob's next keys in - // period 455 - kA = transportCrypto.rotateTransportKeys(kA, 456); - kB = transportCrypto.rotateTransportKeys(kB, 455); - // Alice's outgoing keys should equal Bob's next incoming keys - assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(), - kB.getNextIncomingKeys().getTagKey().getBytes()); - assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(), - kB.getNextIncomingKeys().getHeaderKey().getBytes()); - // Compare Alice's next keys in period 456 with Bob's current keys - // in period 457 - kB = transportCrypto.rotateTransportKeys(kB, 457); - // Alice's next incoming keys should equal Bob's outgoing keys - assertArrayEquals(kA.getNextIncomingKeys().getTagKey().getBytes(), - kB.getCurrentOutgoingKeys().getTagKey().getBytes()); - assertArrayEquals(kA.getNextIncomingKeys().getHeaderKey().getBytes(), - kB.getCurrentOutgoingKeys().getHeaderKey().getBytes()); - } - - @Test - public void testMasterKeyAffectsOutput() { - SecretKey master1 = getSecretKey(); - assertFalse(Arrays.equals(master.getBytes(), master1.getBytes())); - TransportKeys k = transportCrypto.deriveTransportKeys(transportId, - master, 123, true, true); - TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId, - master1, 123, true, true); - assertAllDifferent(k, k1); - } - - @Test - public void testTransportIdAffectsOutput() { - TransportId transportId1 = getTransportId(); - assertFalse(transportId.getString().equals(transportId1.getString())); - TransportKeys k = transportCrypto.deriveTransportKeys(transportId, - master, 123, true, true); - TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1, - master, 123, true, true); - assertAllDifferent(k, k1); - } - - private void assertAllDifferent(TransportKeys... transportKeys) { - List secretKeys = new ArrayList<>(); - for (TransportKeys k : transportKeys) { - secretKeys.add(k.getPreviousIncomingKeys().getTagKey()); - secretKeys.add(k.getPreviousIncomingKeys().getHeaderKey()); - secretKeys.add(k.getCurrentIncomingKeys().getTagKey()); - secretKeys.add(k.getCurrentIncomingKeys().getHeaderKey()); - secretKeys.add(k.getNextIncomingKeys().getTagKey()); - secretKeys.add(k.getNextIncomingKeys().getHeaderKey()); - secretKeys.add(k.getCurrentOutgoingKeys().getTagKey()); - secretKeys.add(k.getCurrentOutgoingKeys().getHeaderKey()); - } - assertAllDifferent(secretKeys); - } - - private void assertAllDifferent(List keys) { - Set set = new HashSet<>(); - for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes()))); - } -} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTestUtils.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTestUtils.java new file mode 100644 index 000000000..878dc5488 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyDerivationTestUtils.java @@ -0,0 +1,45 @@ +package org.briarproject.bramble.crypto; + +import org.briarproject.bramble.api.Bytes; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.transport.AbstractTransportKeys; +import org.briarproject.bramble.api.transport.IncomingKeys; +import org.briarproject.bramble.api.transport.OutgoingKeys; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; + +class KeyDerivationTestUtils { + + static void assertAllDifferent(AbstractTransportKeys... transportKeys) { + List secretKeys = new ArrayList<>(); + for (AbstractTransportKeys k : transportKeys) { + secretKeys.add(k.getPreviousIncomingKeys().getTagKey()); + secretKeys.add(k.getPreviousIncomingKeys().getHeaderKey()); + secretKeys.add(k.getCurrentIncomingKeys().getTagKey()); + secretKeys.add(k.getCurrentIncomingKeys().getHeaderKey()); + secretKeys.add(k.getNextIncomingKeys().getTagKey()); + secretKeys.add(k.getNextIncomingKeys().getHeaderKey()); + secretKeys.add(k.getCurrentOutgoingKeys().getTagKey()); + secretKeys.add(k.getCurrentOutgoingKeys().getHeaderKey()); + } + assertAllDifferent(secretKeys); + } + + static void assertAllDifferent(List keys) { + Set set = new HashSet<>(); + for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes()))); + } + + static void assertMatches(IncomingKeys in, OutgoingKeys out) { + assertArrayEquals(in.getTagKey().getBytes(), + out.getTagKey().getBytes()); + assertArrayEquals(in.getHeaderKey().getBytes(), + out.getHeaderKey().getBytes()); + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/TransportKeyDerivationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/TransportKeyDerivationTest.java new file mode 100644 index 000000000..bfa031e73 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/TransportKeyDerivationTest.java @@ -0,0 +1,167 @@ +package org.briarproject.bramble.crypto; + +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.crypto.TransportCrypto; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.transport.TransportKeys; +import org.briarproject.bramble.test.BrambleTestCase; +import org.briarproject.bramble.test.TestSecureRandomProvider; +import org.junit.Test; + +import java.util.Arrays; + +import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertAllDifferent; +import static org.briarproject.bramble.crypto.KeyDerivationTestUtils.assertMatches; +import static org.briarproject.bramble.test.TestUtils.getSecretKey; +import static org.briarproject.bramble.test.TestUtils.getTransportId; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertSame; + +public class TransportKeyDerivationTest extends BrambleTestCase { + + private final CryptoComponent crypto = + new CryptoComponentImpl(new TestSecureRandomProvider(), null); + private final TransportCrypto transportCrypto = + new TransportCryptoImpl(crypto); + private final TransportId transportId = getTransportId(); + private final SecretKey rootKey = getSecretKey(); + + @Test + public void testKeysAreDistinct() { + TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, false, true); + assertAllDifferent(kA); + assertAllDifferent(kB); + } + + @Test + public void testKeysAreNotRotatedToPreviousPeriod() { + TransportKeys k = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 122); + assertSame(k, k1); + } + + @Test + public void testKeysAreNotRotatedToCurrentPeriod() { + TransportKeys k = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 123); + assertSame(k, k1); + } + + @Test + public void testKeysAreRotatedByOnePeriod() { + TransportKeys k = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 124); + assertSame(k.getCurrentIncomingKeys(), k1.getPreviousIncomingKeys()); + assertSame(k.getNextIncomingKeys(), k1.getCurrentIncomingKeys()); + } + + @Test + public void testKeysAreRotatedByTwoPeriods() { + TransportKeys k = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 125); + assertSame(k.getNextIncomingKeys(), k1.getPreviousIncomingKeys()); + } + + @Test + public void testKeysAreRotatedByThreePeriods() { + TransportKeys k = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys k1 = transportCrypto.rotateTransportKeys(k, 126); + assertAllDifferent(k, k1); + } + + @Test + public void testCurrentKeysMatchContact() { + // Start in time period 123 + TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, false, true); + // Alice's incoming keys should equal Bob's outgoing keys + assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys()); + // Bob's incoming keys should equal Alice's outgoing keys + assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys()); + // Rotate into the future + kA = transportCrypto.rotateTransportKeys(kA, 456); + kB = transportCrypto.rotateTransportKeys(kB, 456); + // Alice's incoming keys should equal Bob's outgoing keys + assertMatches(kA.getCurrentIncomingKeys(), kB.getCurrentOutgoingKeys()); + // Bob's incoming keys should equal Alice's outgoing keys + assertMatches(kB.getCurrentIncomingKeys(), kA.getCurrentOutgoingKeys()); + } + + @Test + public void testPreviousKeysMatchContact() { + // Start in time period 123 + TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, false, true); + // Compare Alice's previous keys in period 456 with Bob's current keys + // in period 455 + kA = transportCrypto.rotateTransportKeys(kA, 456); + kB = transportCrypto.rotateTransportKeys(kB, 455); + // Alice's previous incoming keys should equal Bob's current + // outgoing keys + assertMatches(kA.getPreviousIncomingKeys(), + kB.getCurrentOutgoingKeys()); + // Compare Alice's current keys in period 456 with Bob's previous keys + // in period 457 + kB = transportCrypto.rotateTransportKeys(kB, 457); + // Bob's previous incoming keys should equal Alice's current + // outgoing keys + assertMatches(kB.getPreviousIncomingKeys(), + kA.getCurrentOutgoingKeys()); + } + + @Test + public void testNextKeysMatchContact() { + // Start in time period 123 + TransportKeys kA = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys kB = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, false, true); + // Compare Alice's current keys in period 456 with Bob's next keys in + // period 455 + kA = transportCrypto.rotateTransportKeys(kA, 456); + kB = transportCrypto.rotateTransportKeys(kB, 455); + // Bob's next incoming keys should equal Alice's current outgoing keys + assertMatches(kB.getNextIncomingKeys(), kA.getCurrentOutgoingKeys()); + // Compare Alice's next keys in period 456 with Bob's current keys + // in period 457 + kB = transportCrypto.rotateTransportKeys(kB, 457); + // Alice's next incoming keys should equal Bob's current outgoing keys + assertMatches(kA.getNextIncomingKeys(), kB.getCurrentOutgoingKeys()); + } + + @Test + public void testRootKeyAffectsOutput() { + SecretKey rootKey1 = getSecretKey(); + assertFalse(Arrays.equals(rootKey.getBytes(), rootKey1.getBytes())); + TransportKeys k = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId, + rootKey1, 123, true, true); + assertAllDifferent(k, k1); + } + + @Test + public void testTransportIdAffectsOutput() { + TransportId transportId1 = getTransportId(); + assertNotEquals(transportId.getString(), transportId1.getString()); + TransportKeys k = transportCrypto.deriveTransportKeys(transportId, + rootKey, 123, true, true); + TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1, + rootKey, 123, true, true); + assertAllDifferent(k, k1); + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java index d230ea878..e9a503a19 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java @@ -2,6 +2,7 @@ package org.briarproject.bramble.db; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.event.ContactAddedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent; @@ -13,6 +14,7 @@ import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.NoSuchLocalAuthorException; import org.briarproject.bramble.api.db.NoSuchMessageException; +import org.briarproject.bramble.api.db.NoSuchPendingContactException; import org.briarproject.bramble.api.db.NoSuchTransportException; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventBus; @@ -44,10 +46,11 @@ import org.briarproject.bramble.api.sync.event.MessageToAckEvent; import org.briarproject.bramble.api.sync.event.MessageToRequestEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesSentEvent; +import org.briarproject.bramble.api.transport.HandshakeKeys; import org.briarproject.bramble.api.transport.IncomingKeys; -import org.briarproject.bramble.api.transport.KeySet; -import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.OutgoingKeys; +import org.briarproject.bramble.api.transport.TransportKeySet; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.CaptureArgumentAction; @@ -111,7 +114,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { private final int maxLatency; private final ContactId contactId; private final Contact contact; - private final KeySetId keySetId; + private final TransportKeySetId keySetId; + private final PendingContactId pendingContactId; public DatabaseComponentImplTest() { clientId = getClientId(); @@ -132,7 +136,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { contactId = new ContactId(234); contact = new Contact(contactId, author, localAuthor.getId(), alias, true, true); - keySetId = new KeySetId(345); + keySetId = new TransportKeySetId(345); + pendingContactId = new PendingContactId(getRandomId()); } private DatabaseComponent createDatabaseComponent(Database database, @@ -279,15 +284,24 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { throws Exception { context.checking(new Expectations() {{ // Check whether the contact is in the DB (which it's not) - exactly(17).of(database).startTransaction(); + exactly(18).of(database).startTransaction(); will(returnValue(txn)); - exactly(17).of(database).containsContact(txn, contactId); + exactly(18).of(database).containsContact(txn, contactId); will(returnValue(false)); - exactly(17).of(database).abortTransaction(txn); + exactly(18).of(database).abortTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, eventExecutor, shutdownManager); + try { + db.transaction(false, transaction -> + db.addHandshakeKeys(transaction, contactId, + createHandshakeKeys())); + fail(); + } catch (NoSuchContactException expected) { + // Expected + } + try { db.transaction(false, transaction -> db.addTransportKeys(transaction, contactId, @@ -718,6 +732,39 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { } } + @Test + public void testVariousMethodsThrowExceptionIfPendingContactIsMissing() + throws Exception { + context.checking(new Expectations() {{ + // Check whether the pending contact is in the DB (which it's not) + exactly(2).of(database).startTransaction(); + will(returnValue(txn)); + exactly(2).of(database).containsPendingContact(txn, + pendingContactId); + will(returnValue(false)); + exactly(2).of(database).abortTransaction(txn); + }}); + DatabaseComponent db = createDatabaseComponent(database, eventBus, + eventExecutor, shutdownManager); + + try { + db.transaction(false, transaction -> + db.addHandshakeKeys(transaction, pendingContactId, + createHandshakeKeys())); + fail(); + } catch (NoSuchPendingContactException expected) { + // Expected + } + + try { + db.transaction(false, transaction -> + db.removePendingContact(transaction, pendingContactId)); + fail(); + } catch (NoSuchPendingContactException expected) { + // Expected + } + } + @Test public void testGenerateAck() throws Exception { Collection messagesToAck = asList(messageId, messageId1); @@ -1117,8 +1164,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { @Test public void testTransportKeys() throws Exception { TransportKeys transportKeys = createTransportKeys(); - KeySet ks = new KeySet(keySetId, contactId, transportKeys); - Collection keys = singletonList(ks); + TransportKeySet ks = + new TransportKeySet(keySetId, contactId, transportKeys); + Collection keys = singletonList(ks); context.checking(new Expectations() {{ // startTransaction() @@ -1245,6 +1293,27 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { }); } + private HandshakeKeys createHandshakeKeys() { + SecretKey inPrevTagKey = getSecretKey(); + SecretKey inPrevHeaderKey = getSecretKey(); + IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey, + 1, 123, new byte[4]); + SecretKey inCurrTagKey = getSecretKey(); + SecretKey inCurrHeaderKey = getSecretKey(); + IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey, + 2, 234, new byte[4]); + SecretKey inNextTagKey = getSecretKey(); + SecretKey inNextHeaderKey = getSecretKey(); + IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey, + 3, 345, new byte[4]); + SecretKey outCurrTagKey = getSecretKey(); + SecretKey outCurrHeaderKey = getSecretKey(); + OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey, + 2, 456, true); + return new HandshakeKeys(transportId, inPrev, inCurr, inNext, outCurr, + getSecretKey(), true); + } + private TransportKeys createTransportKeys() { SecretKey inPrevTagKey = getSecretKey(); SecretKey inPrevHeaderKey = getSecretKey(); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java index b303b5060..1687ec75a 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java @@ -2,6 +2,7 @@ package org.briarproject.bramble.db; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DbException; @@ -20,10 +21,13 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.transport.HandshakeKeySet; +import org.briarproject.bramble.api.transport.HandshakeKeySetId; +import org.briarproject.bramble.api.transport.HandshakeKeys; import org.briarproject.bramble.api.transport.IncomingKeys; -import org.briarproject.bramble.api.transport.KeySet; -import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.OutgoingKeys; +import org.briarproject.bramble.api.transport.TransportKeySet; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.system.SystemClock; import org.briarproject.bramble.test.BrambleTestCase; @@ -52,6 +56,7 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.briarproject.bramble.api.contact.PendingContactState.FAILED; import static org.briarproject.bramble.api.db.Metadata.REMOVE; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; @@ -70,6 +75,7 @@ import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getMessage; +import static org.briarproject.bramble.test.TestUtils.getPendingContact; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getTestDirectory; @@ -90,7 +96,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // All our transports use a maximum latency of 30 seconds private static final int MAX_LATENCY = 30 * 1000; - private final SecretKey key = getSecretKey(); private final File testDir = getTestDirectory(); private final GroupId groupId; @@ -103,7 +108,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { private final MessageId messageId; private final TransportId transportId; private final ContactId contactId; - private final KeySetId keySetId, keySetId1; + private final TransportKeySetId keySetId, keySetId1; + private final HandshakeKeySetId handshakeKeySetId, handshakeKeySetId1; + private final PendingContact pendingContact; private final Random random = new Random(); JdbcDatabaseTest() { @@ -117,8 +124,11 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { messageId = message.getId(); transportId = getTransportId(); contactId = new ContactId(1); - keySetId = new KeySetId(1); - keySetId1 = new KeySetId(2); + keySetId = new TransportKeySetId(1); + keySetId1 = new TransportKeySetId(2); + handshakeKeySetId = new HandshakeKeySetId(1); + handshakeKeySetId1 = new HandshakeKeySetId(2); + pendingContact = getPendingContact(); } protected abstract JdbcDatabase createDatabase(DatabaseConfig config, @@ -653,10 +663,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { @Test public void testTransportKeys() throws Exception { - long rotationPeriod = 123, rotationPeriod1 = 234; + long timePeriod = 123, timePeriod1 = 234; boolean active = random.nextBoolean(); - TransportKeys keys = createTransportKeys(rotationPeriod, active); - TransportKeys keys1 = createTransportKeys(rotationPeriod1, active); + TransportKeys keys = createTransportKeys(timePeriod, active); + TransportKeys keys1 = createTransportKeys(timePeriod1, active); Database db = open(false); Connection txn = db.startTransaction(); @@ -673,35 +683,38 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1)); // Retrieve the transport keys - Collection allKeys = db.getTransportKeys(txn, transportId); + Collection allKeys = + db.getTransportKeys(txn, transportId); assertEquals(2, allKeys.size()); - for (KeySet ks : allKeys) { + for (TransportKeySet ks : allKeys) { assertEquals(contactId, ks.getContactId()); if (ks.getKeySetId().equals(keySetId)) { - assertKeysEquals(keys, ks.getTransportKeys()); + assertKeysEquals(keys, ks.getKeys()); } else { assertEquals(keySetId1, ks.getKeySetId()); - assertKeysEquals(keys1, ks.getTransportKeys()); + assertKeysEquals(keys1, ks.getKeys()); } } // Rotate the transport keys - TransportKeys rotated = createTransportKeys(rotationPeriod + 1, active); + TransportKeys rotated = createTransportKeys(timePeriod + 1, active); TransportKeys rotated1 = - createTransportKeys(rotationPeriod1 + 1, active); - db.updateTransportKeys(txn, new KeySet(keySetId, contactId, rotated)); - db.updateTransportKeys(txn, new KeySet(keySetId1, contactId, rotated1)); + createTransportKeys(timePeriod1 + 1, active); + db.updateTransportKeys(txn, new TransportKeySet(keySetId, contactId, + rotated)); + db.updateTransportKeys(txn, new TransportKeySet(keySetId1, contactId, + rotated1)); // Retrieve the transport keys again allKeys = db.getTransportKeys(txn, transportId); assertEquals(2, allKeys.size()); - for (KeySet ks : allKeys) { + for (TransportKeySet ks : allKeys) { assertEquals(contactId, ks.getContactId()); if (ks.getKeySetId().equals(keySetId)) { - assertKeysEquals(rotated, ks.getTransportKeys()); + assertKeysEquals(rotated, ks.getKeys()); } else { assertEquals(keySetId1, ks.getKeySetId()); - assertKeysEquals(rotated1, ks.getTransportKeys()); + assertKeysEquals(rotated1, ks.getKeys()); } } @@ -716,7 +729,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { private void assertKeysEquals(TransportKeys expected, TransportKeys actual) { assertEquals(expected.getTransportId(), actual.getTransportId()); - assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod()); + assertEquals(expected.getTimePeriod(), actual.getTimePeriod()); assertKeysEquals(expected.getPreviousIncomingKeys(), actual.getPreviousIncomingKeys()); assertKeysEquals(expected.getCurrentIncomingKeys(), @@ -732,7 +745,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { actual.getTagKey().getBytes()); assertArrayEquals(expected.getHeaderKey().getBytes(), actual.getHeaderKey().getBytes()); - assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod()); + assertEquals(expected.getTimePeriod(), actual.getTimePeriod()); assertEquals(expected.getWindowBase(), actual.getWindowBase()); assertArrayEquals(expected.getWindowBitmap(), actual.getWindowBitmap()); } @@ -742,15 +755,174 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { actual.getTagKey().getBytes()); assertArrayEquals(expected.getHeaderKey().getBytes(), actual.getHeaderKey().getBytes()); - assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod()); + assertEquals(expected.getTimePeriod(), actual.getTimePeriod()); assertEquals(expected.getStreamCounter(), actual.getStreamCounter()); assertEquals(expected.isActive(), actual.isActive()); } + @Test + public void testHandshakeKeys() throws Exception { + long timePeriod = 123, timePeriod1 = 234; + boolean alice = random.nextBoolean(); + SecretKey rootKey = getSecretKey(); + SecretKey rootKey1 = getSecretKey(); + HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice); + HandshakeKeys keys1 = createHandshakeKeys(timePeriod1, rootKey1, alice); + + Database db = open(false); + Connection txn = db.startTransaction(); + + // Initially there should be no handshake keys in the database + assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId)); + + // Add the contact, the transport and the handshake keys + db.addLocalAuthor(txn, localAuthor); + assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(), + true, true)); + db.addTransport(txn, transportId, 123); + assertEquals(handshakeKeySetId, + db.addHandshakeKeys(txn, contactId, keys)); + assertEquals(handshakeKeySetId1, + db.addHandshakeKeys(txn, contactId, keys1)); + + // Retrieve the handshake keys + Collection allKeys = + db.getHandshakeKeys(txn, transportId); + assertEquals(2, allKeys.size()); + for (HandshakeKeySet ks : allKeys) { + assertEquals(contactId, ks.getContactId()); + assertNull(ks.getPendingContactId()); + if (ks.getKeySetId().equals(handshakeKeySetId)) { + assertKeysEquals(keys, ks.getKeys()); + } else { + assertEquals(handshakeKeySetId1, ks.getKeySetId()); + assertKeysEquals(keys1, ks.getKeys()); + } + } + + // Update the handshake keys + HandshakeKeys updated = + createHandshakeKeys(timePeriod + 1, rootKey, alice); + HandshakeKeys updated1 = + createHandshakeKeys(timePeriod1 + 1, rootKey1, alice); + db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId, + contactId, updated)); + db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId1, + contactId, updated1)); + + // Retrieve the handshake keys again + allKeys = db.getHandshakeKeys(txn, transportId); + assertEquals(2, allKeys.size()); + for (HandshakeKeySet ks : allKeys) { + assertEquals(contactId, ks.getContactId()); + assertNull(ks.getPendingContactId()); + if (ks.getKeySetId().equals(handshakeKeySetId)) { + assertKeysEquals(updated, ks.getKeys()); + } else { + assertEquals(handshakeKeySetId1, ks.getKeySetId()); + assertKeysEquals(updated1, ks.getKeys()); + } + } + + // Removing the contact should remove the handshake keys + db.removeContact(txn, contactId); + assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId)); + + db.commitTransaction(txn); + db.close(); + } + + private void assertKeysEquals(HandshakeKeys expected, + HandshakeKeys actual) { + assertEquals(expected.getTransportId(), actual.getTransportId()); + assertEquals(expected.getTimePeriod(), actual.getTimePeriod()); + assertArrayEquals(expected.getRootKey().getBytes(), + actual.getRootKey().getBytes()); + assertEquals(expected.isAlice(), actual.isAlice()); + assertKeysEquals(expected.getPreviousIncomingKeys(), + actual.getPreviousIncomingKeys()); + assertKeysEquals(expected.getCurrentIncomingKeys(), + actual.getCurrentIncomingKeys()); + assertKeysEquals(expected.getNextIncomingKeys(), + actual.getNextIncomingKeys()); + assertKeysEquals(expected.getCurrentOutgoingKeys(), + actual.getCurrentOutgoingKeys()); + } + + @Test + public void testHandshakeKeysForPendingContact() throws Exception { + long timePeriod = 123, timePeriod1 = 234; + boolean alice = random.nextBoolean(); + SecretKey rootKey = getSecretKey(); + SecretKey rootKey1 = getSecretKey(); + HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice); + HandshakeKeys keys1 = createHandshakeKeys(timePeriod1, rootKey1, alice); + + Database db = open(false); + Connection txn = db.startTransaction(); + + // Initially there should be no handshake keys in the database + assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId)); + + // Add the pending contact, the transport and the handshake keys + db.addPendingContact(txn, pendingContact); + db.addTransport(txn, transportId, 123); + assertEquals(handshakeKeySetId, db.addHandshakeKeys(txn, + pendingContact.getId(), keys)); + assertEquals(handshakeKeySetId1, db.addHandshakeKeys(txn, + pendingContact.getId(), keys1)); + + // Retrieve the handshake keys + Collection allKeys = + db.getHandshakeKeys(txn, transportId); + assertEquals(2, allKeys.size()); + for (HandshakeKeySet ks : allKeys) { + assertNull(ks.getContactId()); + assertEquals(pendingContact.getId(), ks.getPendingContactId()); + if (ks.getKeySetId().equals(handshakeKeySetId)) { + assertKeysEquals(keys, ks.getKeys()); + } else { + assertEquals(handshakeKeySetId1, ks.getKeySetId()); + assertKeysEquals(keys1, ks.getKeys()); + } + } + + // Update the handshake keys + HandshakeKeys updated = + createHandshakeKeys(timePeriod + 1, rootKey, alice); + HandshakeKeys updated1 = + createHandshakeKeys(timePeriod1 + 1, rootKey1, alice); + db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId, + pendingContact.getId(), updated)); + db.updateHandshakeKeys(txn, new HandshakeKeySet(handshakeKeySetId1, + pendingContact.getId(), updated1)); + + // Retrieve the handshake keys again + allKeys = db.getHandshakeKeys(txn, transportId); + assertEquals(2, allKeys.size()); + for (HandshakeKeySet ks : allKeys) { + assertNull(ks.getContactId()); + assertEquals(pendingContact.getId(), ks.getPendingContactId()); + if (ks.getKeySetId().equals(handshakeKeySetId)) { + assertKeysEquals(updated, ks.getKeys()); + } else { + assertEquals(handshakeKeySetId1, ks.getKeySetId()); + assertKeysEquals(updated1, ks.getKeys()); + } + } + + // Removing the pending contact should remove the handshake keys + db.removePendingContact(txn, pendingContact.getId()); + assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId)); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testIncrementStreamCounter() throws Exception { - long rotationPeriod = 123; - TransportKeys keys = createTransportKeys(rotationPeriod, true); + long timePeriod = 123; + TransportKeys keys = createTransportKeys(timePeriod, true); long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter(); Database db = open(false); @@ -766,15 +938,63 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // Increment the stream counter twice and retrieve the transport keys db.incrementStreamCounter(txn, transportId, keySetId); db.incrementStreamCounter(txn, transportId, keySetId); - Collection newKeys = db.getTransportKeys(txn, transportId); + Collection newKeys = + db.getTransportKeys(txn, transportId); assertEquals(1, newKeys.size()); - KeySet ks = newKeys.iterator().next(); + TransportKeySet ks = newKeys.iterator().next(); assertEquals(keySetId, ks.getKeySetId()); assertEquals(contactId, ks.getContactId()); - TransportKeys k = ks.getTransportKeys(); + TransportKeys k = ks.getKeys(); assertEquals(transportId, k.getTransportId()); OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); - assertEquals(rotationPeriod, outCurr.getRotationPeriod()); + assertEquals(timePeriod, outCurr.getTimePeriod()); + assertEquals(streamCounter + 2, outCurr.getStreamCounter()); + + // The rest of the keys should be unaffected + assertKeysEquals(keys.getPreviousIncomingKeys(), + k.getPreviousIncomingKeys()); + assertKeysEquals(keys.getCurrentIncomingKeys(), + k.getCurrentIncomingKeys()); + assertKeysEquals(keys.getNextIncomingKeys(), k.getNextIncomingKeys()); + + db.commitTransaction(txn); + db.close(); + } + + @Test + public void testIncrementStreamCounterForHandshakeKeys() throws Exception { + long timePeriod = 123; + SecretKey rootKey = getSecretKey(); + boolean alice = random.nextBoolean(); + HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice); + long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter(); + + Database db = open(false); + Connection txn = db.startTransaction(); + + // Add the contact, transport and handshake keys + db.addLocalAuthor(txn, localAuthor); + assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(), + true, true)); + db.addTransport(txn, transportId, 123); + assertEquals(handshakeKeySetId, + db.addHandshakeKeys(txn, contactId, keys)); + + // Increment the stream counter twice and retrieve the handshake keys + db.incrementStreamCounter(txn, transportId, handshakeKeySetId); + db.incrementStreamCounter(txn, transportId, handshakeKeySetId); + Collection newKeys = + db.getHandshakeKeys(txn, transportId); + assertEquals(1, newKeys.size()); + HandshakeKeySet ks = newKeys.iterator().next(); + assertEquals(handshakeKeySetId, ks.getKeySetId()); + assertEquals(contactId, ks.getContactId()); + HandshakeKeys k = ks.getKeys(); + assertEquals(transportId, k.getTransportId()); + assertArrayEquals(rootKey.getBytes(), k.getRootKey().getBytes()); + assertEquals(alice, k.isAlice()); + OutgoingKeys outCurr = k.getCurrentOutgoingKeys(); + assertEquals(timePeriod, outCurr.getTimePeriod()); assertEquals(streamCounter + 2, outCurr.getStreamCounter()); // The rest of the keys should be unaffected @@ -791,8 +1011,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { @Test public void testSetReorderingWindow() throws Exception { boolean active = random.nextBoolean(); - long rotationPeriod = 123; - TransportKeys keys = createTransportKeys(rotationPeriod, active); + long timePeriod = 123; + TransportKeys keys = createTransportKeys(timePeriod, active); long base = keys.getCurrentIncomingKeys().getWindowBase(); byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap(); @@ -808,17 +1028,68 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { // Update the reordering window and retrieve the transport keys random.nextBytes(bitmap); - db.setReorderingWindow(txn, keySetId, transportId, rotationPeriod, + db.setReorderingWindow(txn, keySetId, transportId, timePeriod, base + 1, bitmap); - Collection newKeys = db.getTransportKeys(txn, transportId); + Collection newKeys = + db.getTransportKeys(txn, transportId); assertEquals(1, newKeys.size()); - KeySet ks = newKeys.iterator().next(); + TransportKeySet ks = newKeys.iterator().next(); assertEquals(keySetId, ks.getKeySetId()); assertEquals(contactId, ks.getContactId()); - TransportKeys k = ks.getTransportKeys(); + TransportKeys k = ks.getKeys(); assertEquals(transportId, k.getTransportId()); IncomingKeys inCurr = k.getCurrentIncomingKeys(); - assertEquals(rotationPeriod, inCurr.getRotationPeriod()); + assertEquals(timePeriod, inCurr.getTimePeriod()); + assertEquals(base + 1, inCurr.getWindowBase()); + assertArrayEquals(bitmap, inCurr.getWindowBitmap()); + + // The rest of the keys should be unaffected + assertKeysEquals(keys.getPreviousIncomingKeys(), + k.getPreviousIncomingKeys()); + assertKeysEquals(keys.getNextIncomingKeys(), k.getNextIncomingKeys()); + assertKeysEquals(keys.getCurrentOutgoingKeys(), + k.getCurrentOutgoingKeys()); + + db.commitTransaction(txn); + db.close(); + } + + @Test + public void testSetReorderingWindowForHandshakeKeys() throws Exception { + long timePeriod = 123; + SecretKey rootKey = getSecretKey(); + boolean alice = random.nextBoolean(); + HandshakeKeys keys = createHandshakeKeys(timePeriod, rootKey, alice); + long base = keys.getCurrentIncomingKeys().getWindowBase(); + byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap(); + + Database db = open(false); + Connection txn = db.startTransaction(); + + // Add the contact, transport and handshake keys + db.addLocalAuthor(txn, localAuthor); + assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(), + true, true)); + db.addTransport(txn, transportId, 123); + assertEquals(handshakeKeySetId, + db.addHandshakeKeys(txn, contactId, keys)); + + // Update the reordering window and retrieve the handshake keys + random.nextBytes(bitmap); + db.setReorderingWindow(txn, handshakeKeySetId, transportId, timePeriod, + base + 1, bitmap); + Collection newKeys = + db.getHandshakeKeys(txn, transportId); + assertEquals(1, newKeys.size()); + HandshakeKeySet ks = newKeys.iterator().next(); + assertEquals(handshakeKeySetId, ks.getKeySetId()); + assertEquals(contactId, ks.getContactId()); + HandshakeKeys k = ks.getKeys(); + assertEquals(transportId, k.getTransportId()); + assertArrayEquals(rootKey.getBytes(), k.getRootKey().getBytes()); + assertEquals(alice, k.isAlice()); + IncomingKeys inCurr = k.getCurrentIncomingKeys(); + assertEquals(timePeriod, inCurr.getTimePeriod()); assertEquals(base + 1, inCurr.getWindowBase()); assertArrayEquals(bitmap, inCurr.getWindowBitmap()); @@ -1965,6 +2236,39 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.close(); } + @Test + public void testPendingContacts() throws Exception { + Database db = open(false); + Connection txn = db.startTransaction(); + + assertEquals(emptyList(), db.getPendingContacts(txn)); + + db.addPendingContact(txn, pendingContact); + Collection pendingContacts = + db.getPendingContacts(txn); + assertEquals(1, pendingContacts.size()); + PendingContact retrieved = pendingContacts.iterator().next(); + assertEquals(pendingContact.getId(), retrieved.getId()); + assertEquals(pendingContact.getAlias(), retrieved.getAlias()); + assertEquals(pendingContact.getState(), retrieved.getState()); + assertEquals(pendingContact.getTimestamp(), retrieved.getTimestamp()); + + db.setPendingContactState(txn, pendingContact.getId(), FAILED); + pendingContacts = db.getPendingContacts(txn); + assertEquals(1, pendingContacts.size()); + retrieved = pendingContacts.iterator().next(); + assertEquals(pendingContact.getId(), retrieved.getId()); + assertEquals(pendingContact.getAlias(), retrieved.getAlias()); + assertEquals(FAILED, retrieved.getState()); + assertEquals(pendingContact.getTimestamp(), retrieved.getTimestamp()); + + db.removePendingContact(txn, pendingContact.getId()); + assertEquals(emptyList(), db.getPendingContacts(txn)); + + db.commitTransaction(txn); + db.close(); + } + private Database open(boolean resume) throws Exception { return open(resume, new TestMessageFactory(), new SystemClock()); } @@ -1978,27 +2282,48 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { return db; } - private TransportKeys createTransportKeys(long rotationPeriod, - boolean active) { + private TransportKeys createTransportKeys(long timePeriod, boolean active) { SecretKey inPrevTagKey = getSecretKey(); SecretKey inPrevHeaderKey = getSecretKey(); IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey, - rotationPeriod - 1, 123, new byte[4]); + timePeriod - 1, 123, new byte[4]); SecretKey inCurrTagKey = getSecretKey(); SecretKey inCurrHeaderKey = getSecretKey(); IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey, - rotationPeriod, 234, new byte[4]); + timePeriod, 234, new byte[4]); SecretKey inNextTagKey = getSecretKey(); SecretKey inNextHeaderKey = getSecretKey(); IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey, - rotationPeriod + 1, 345, new byte[4]); + timePeriod + 1, 345, new byte[4]); SecretKey outCurrTagKey = getSecretKey(); SecretKey outCurrHeaderKey = getSecretKey(); OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey, - rotationPeriod, 456, active); + timePeriod, 456, active); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); } + private HandshakeKeys createHandshakeKeys(long timePeriod, + SecretKey rootKey, boolean alice) { + SecretKey inPrevTagKey = getSecretKey(); + SecretKey inPrevHeaderKey = getSecretKey(); + IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey, + timePeriod - 1, 123, new byte[4]); + SecretKey inCurrTagKey = getSecretKey(); + SecretKey inCurrHeaderKey = getSecretKey(); + IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey, + timePeriod, 234, new byte[4]); + SecretKey inNextTagKey = getSecretKey(); + SecretKey inNextHeaderKey = getSecretKey(); + IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey, + timePeriod + 1, 345, new byte[4]); + SecretKey outCurrTagKey = getSecretKey(); + SecretKey outCurrHeaderKey = getSecretKey(); + OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey, + timePeriod, 456, true); + return new HandshakeKeys(transportId, inPrev, inCurr, inNext, outCurr, + rootKey, alice); + } + @After public void tearDown() { deleteTestDirectory(testDir); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocolTest.java b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocolTest.java index e070db6a9..516f7f1b2 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocolTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/KeyAgreementProtocolTest.java @@ -17,7 +17,7 @@ import org.junit.Rule; import org.junit.Test; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; -import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL; +import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_KEY_LABEL; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; @@ -74,7 +74,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase { Payload ourPayload = new Payload(aliceCommit, null); KeyPair ourKeyPair = new KeyPair(ourPubKey, null); SecretKey sharedSecret = getSecretKey(); - SecretKey masterSecret = getSecretKey(); + SecretKey masterKey = getSecretKey(); KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks, crypto, keyAgreementCrypto, payloadEncoder, transport, @@ -134,13 +134,13 @@ public class KeyAgreementProtocolTest extends BrambleTestCase { true, false); will(returnValue(bobConfirm)); - // Alice computes master secret - oneOf(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret); - will(returnValue(masterSecret)); + // Alice derives master key + oneOf(crypto).deriveKey(MASTER_KEY_LABEL, sharedSecret); + will(returnValue(masterKey)); }}); // execute - assertThat(masterSecret, is(equalTo(protocol.perform()))); + assertThat(masterKey, is(equalTo(protocol.perform()))); } @Test @@ -150,7 +150,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase { Payload ourPayload = new Payload(bobCommit, null); KeyPair ourKeyPair = new KeyPair(ourPubKey, null); SecretKey sharedSecret = getSecretKey(); - SecretKey masterSecret = getSecretKey(); + SecretKey masterKey = getSecretKey(); KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks, crypto, keyAgreementCrypto, payloadEncoder, transport, @@ -209,13 +209,13 @@ public class KeyAgreementProtocolTest extends BrambleTestCase { will(returnValue(bobConfirm)); oneOf(transport).sendConfirm(bobConfirm); - // Bob computes master secret - oneOf(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret); - will(returnValue(masterSecret)); + // Bob derives master key + oneOf(crypto).deriveKey(MASTER_KEY_LABEL, sharedSecret); + will(returnValue(masterKey)); }}); // execute - assertThat(masterSecret, is(equalTo(protocol.perform()))); + assertThat(masterKey, is(equalTo(protocol.perform()))); } @Test(expected = AbortException.class) @@ -373,8 +373,8 @@ public class KeyAgreementProtocolTest extends BrambleTestCase { // Alice aborts oneOf(transport).sendAbort(false); - // Alice never computes master secret - never(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret); + // Alice never derives master key + never(crypto).deriveKey(MASTER_KEY_LABEL, sharedSecret); }}); // execute diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java index c09185f65..d57a3d793 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java @@ -12,8 +12,8 @@ import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; -import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.StreamContext; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.DbExpectations; import org.jmock.Expectations; @@ -51,7 +51,7 @@ public class KeyManagerImplTest extends BrambleMockTestCase { private final Transaction txn = new Transaction(null, false); private final ContactId contactId = new ContactId(123); private final ContactId inactiveContactId = new ContactId(234); - private final KeySetId keySetId = new KeySetId(345); + private final TransportKeySetId keySetId = new TransportKeySetId(345); private final TransportId transportId = getTransportId(); private final TransportId unknownTransportId = getTransportId(); private final StreamContext streamContext = @@ -113,8 +113,8 @@ public class KeyManagerImplTest extends BrambleMockTestCase { will(returnValue(keySetId)); }}); - Map ids = keyManager.addContact(txn, contactId, - secretKey, timestamp, alice, active); + Map ids = keyManager.addContact(txn, + contactId, secretKey, timestamp, alice, active); assertEquals(singletonMap(transportId, keySetId), ids); } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java index 1c13a8107..8dc7531c0 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java @@ -8,10 +8,10 @@ import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.transport.IncomingKeys; -import org.briarproject.bramble.api.transport.KeySet; -import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.StreamContext; +import org.briarproject.bramble.api.transport.TransportKeySet; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.DbExpectations; @@ -57,31 +57,31 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { private final TransportId transportId = getTransportId(); private final long maxLatency = 30 * 1000; // 30 seconds - private final long rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; + private final long timePeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; private final ContactId contactId = new ContactId(123); private final ContactId contactId1 = new ContactId(234); - private final KeySetId keySetId = new KeySetId(345); - private final KeySetId keySetId1 = new KeySetId(456); + private final TransportKeySetId keySetId = new TransportKeySetId(345); + private final TransportKeySetId keySetId1 = new TransportKeySetId(456); private final SecretKey tagKey = TestUtils.getSecretKey(); private final SecretKey headerKey = TestUtils.getSecretKey(); - private final SecretKey masterKey = TestUtils.getSecretKey(); + private final SecretKey rootKey = TestUtils.getSecretKey(); private final Random random = new Random(); @Test public void testKeysAreRotatedAtStartup() throws Exception { TransportKeys shouldRotate = createTransportKeys(900, 0, true); TransportKeys shouldNotRotate = createTransportKeys(1000, 0, true); - Collection loaded = asList( - new KeySet(keySetId, contactId, shouldRotate), - new KeySet(keySetId1, contactId1, shouldNotRotate) + Collection loaded = asList( + new TransportKeySet(keySetId, contactId, shouldRotate), + new TransportKeySet(keySetId1, contactId1, shouldNotRotate) ); TransportKeys rotated = createTransportKeys(1000, 0, true); Transaction txn = new Transaction(null, false); context.checking(new Expectations() {{ - // Get the current time (1 ms after start of rotation period 1000) + // Get the current time (1 ms after start of time period 1000) oneOf(clock).currentTimeMillis(); - will(returnValue(rotationPeriodLength * 1000 + 1)); + will(returnValue(timePeriodLength * 1000 + 1)); // Load the transport keys oneOf(db).getTransportKeys(txn, transportId); will(returnValue(loaded)); @@ -98,11 +98,11 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { will(new EncodeTagAction()); } // Save the keys that were rotated - oneOf(db).updateTransportKeys(txn, - singletonList(new KeySet(keySetId, contactId, rotated))); - // Schedule key rotation at the start of the next rotation period + oneOf(db).updateTransportKeys(txn, singletonList( + new TransportKeySet(keySetId, contactId, rotated))); + // Schedule key rotation at the start of the next time period oneOf(scheduler).schedule(with(any(Runnable.class)), - with(rotationPeriodLength - 1), with(MILLISECONDS)); + with(timePeriodLength - 1), with(MILLISECONDS)); }}); TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( @@ -120,12 +120,12 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { Transaction txn = new Transaction(null, false); context.checking(new Expectations() {{ - oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey, + oneOf(transportCrypto).deriveTransportKeys(transportId, rootKey, 999, alice, true); will(returnValue(transportKeys)); - // Get the current time (1 ms after start of rotation period 1000) + // Get the current time (1 ms after start of time period 1000) oneOf(clock).currentTimeMillis(); - will(returnValue(rotationPeriodLength * 1000 + 1)); + will(returnValue(timePeriodLength * 1000 + 1)); // Rotate the transport keys oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000); will(returnValue(rotated)); @@ -144,10 +144,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( db, transportCrypto, dbExecutor, scheduler, clock, transportId, maxLatency); - // The timestamp is 1 ms before the start of rotation period 1000 - long timestamp = rotationPeriodLength * 1000 - 1; + // The timestamp is 1 ms before the start of time period 1000 + long timestamp = timePeriodLength * 1000 - 1; assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, - masterKey, timestamp, alice, true)); + rootKey, timestamp, alice, true)); assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); } @@ -177,10 +177,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( db, transportCrypto, dbExecutor, scheduler, clock, transportId, maxLatency); - // The timestamp is at the start of rotation period 1000 - long timestamp = rotationPeriodLength * 1000; + // The timestamp is at the start of time period 1000 + long timestamp = timePeriodLength * 1000; assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, - masterKey, timestamp, alice, true)); + rootKey, timestamp, alice, true)); assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId)); } @@ -203,10 +203,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( db, transportCrypto, dbExecutor, scheduler, clock, transportId, maxLatency); - // The timestamp is at the start of rotation period 1000 - long timestamp = rotationPeriodLength * 1000; + // The timestamp is at the start of time period 1000 + long timestamp = timePeriodLength * 1000; assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, - masterKey, timestamp, alice, true)); + rootKey, timestamp, alice, true)); // The first request should return a stream context assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); StreamContext ctx = transportKeyManager.getStreamContext(txn, @@ -235,10 +235,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( db, transportCrypto, dbExecutor, scheduler, clock, transportId, maxLatency); - // The timestamp is at the start of rotation period 1000 - long timestamp = rotationPeriodLength * 1000; + // The timestamp is at the start of time period 1000 + long timestamp = timePeriodLength * 1000; assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, - masterKey, timestamp, alice, active)); + rootKey, timestamp, alice, active)); assertEquals(active, transportKeyManager.canSendOutgoingStreams(contactId)); // The tag should not be recognised @@ -256,12 +256,12 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { List tags = new ArrayList<>(); context.checking(new Expectations() {{ - oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey, + oneOf(transportCrypto).deriveTransportKeys(transportId, rootKey, 1000, alice, true); will(returnValue(transportKeys)); - // Get the current time (the start of rotation period 1000) + // Get the current time (the start of time period 1000) oneOf(clock).currentTimeMillis(); - will(returnValue(rotationPeriodLength * 1000)); + will(returnValue(timePeriodLength * 1000)); // Encode the tags (3 sets) for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) { exactly(3).of(transportCrypto).encodeTag( @@ -280,7 +280,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { with(tagKey), with(PROTOCOL_VERSION), with((long) REORDERING_WINDOW_SIZE)); will(new EncodeTagAction(tags)); - // Save the reordering window (previous rotation period, base 1) + // Save the reordering window (previous time period, base 1) oneOf(db).setReorderingWindow(txn, keySetId, transportId, 999, 1, new byte[REORDERING_WINDOW_SIZE / 8]); }}); @@ -288,12 +288,12 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( db, transportCrypto, dbExecutor, scheduler, clock, transportId, maxLatency); - // The timestamp is at the start of rotation period 1000 - long timestamp = rotationPeriodLength * 1000; + // The timestamp is at the start of time period 1000 + long timestamp = timePeriodLength * 1000; assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, - masterKey, timestamp, alice, true)); + rootKey, timestamp, alice, true)); assertTrue(transportKeyManager.canSendOutgoingStreams(contactId)); - // Use the first tag (previous rotation period, stream number 0) + // Use the first tag (previous time period, stream number 0) assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size()); byte[] tag = tags.get(0); // The first request should return a stream context @@ -313,16 +313,16 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { @Test public void testKeysAreRotatedToCurrentPeriod() throws Exception { TransportKeys transportKeys = createTransportKeys(1000, 0, true); - Collection loaded = - singletonList(new KeySet(keySetId, contactId, transportKeys)); + Collection loaded = singletonList( + new TransportKeySet(keySetId, contactId, transportKeys)); TransportKeys rotated = createTransportKeys(1001, 0, true); Transaction txn = new Transaction(null, false); Transaction txn1 = new Transaction(null, false); context.checking(new DbExpectations() {{ - // Get the current time (the start of rotation period 1000) + // Get the current time (the start of time period 1000) oneOf(clock).currentTimeMillis(); - will(returnValue(rotationPeriodLength * 1000)); + will(returnValue(timePeriodLength * 1000)); // Load the transport keys oneOf(db).getTransportKeys(txn, transportId); will(returnValue(loaded)); @@ -336,17 +336,17 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { with(PROTOCOL_VERSION), with(i)); will(new EncodeTagAction()); } - // Schedule key rotation at the start of the next rotation period + // Schedule key rotation at the start of the next time period oneOf(scheduler).schedule(with(any(Runnable.class)), - with(rotationPeriodLength), with(MILLISECONDS)); + with(timePeriodLength), with(MILLISECONDS)); will(new RunAction()); oneOf(dbExecutor).execute(with(any(Runnable.class))); will(new RunAction()); // Start a transaction for key rotation oneOf(db).transaction(with(false), withDbRunnable(txn1)); - // Get the current time (the start of rotation period 1001) + // Get the current time (the start of time period 1001) oneOf(clock).currentTimeMillis(); - will(returnValue(rotationPeriodLength * 1001)); + will(returnValue(timePeriodLength * 1001)); // Rotate the transport keys oneOf(transportCrypto).rotateTransportKeys( with(any(TransportKeys.class)), with(1001L)); @@ -359,11 +359,11 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { will(new EncodeTagAction()); } // Save the keys that were rotated - oneOf(db).updateTransportKeys(txn1, - singletonList(new KeySet(keySetId, contactId, rotated))); - // Schedule key rotation at the start of the next rotation period + oneOf(db).updateTransportKeys(txn1, singletonList( + new TransportKeySet(keySetId, contactId, rotated))); + // Schedule key rotation at the start of the next time period oneOf(scheduler).schedule(with(any(Runnable.class)), - with(rotationPeriodLength), with(MILLISECONDS)); + with(timePeriodLength), with(MILLISECONDS)); }}); TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( @@ -391,10 +391,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( db, transportCrypto, dbExecutor, scheduler, clock, transportId, maxLatency); - // The timestamp is at the start of rotation period 1000 - long timestamp = rotationPeriodLength * 1000; + // The timestamp is at the start of time period 1000 + long timestamp = timePeriodLength * 1000; assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, - masterKey, timestamp, alice, false)); + rootKey, timestamp, alice, false)); // The keys are inactive so no stream context should be returned assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId)); @@ -421,12 +421,12 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { List tags = new ArrayList<>(); context.checking(new Expectations() {{ - oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey, + oneOf(transportCrypto).deriveTransportKeys(transportId, rootKey, 1000, alice, false); will(returnValue(transportKeys)); - // Get the current time (the start of rotation period 1000) + // Get the current time (the start of time period 1000) oneOf(clock).currentTimeMillis(); - will(returnValue(rotationPeriodLength * 1000)); + will(returnValue(timePeriodLength * 1000)); // Encode the tags (3 sets) for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) { exactly(3).of(transportCrypto).encodeTag( @@ -445,7 +445,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { with(tagKey), with(PROTOCOL_VERSION), with((long) REORDERING_WINDOW_SIZE)); will(new EncodeTagAction(tags)); - // Save the reordering window (previous rotation period, base 1) + // Save the reordering window (previous time period, base 1) oneOf(db).setReorderingWindow(txn, keySetId, transportId, 999, 1, new byte[REORDERING_WINDOW_SIZE / 8]); // Activate the keys @@ -457,10 +457,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { TransportKeyManager transportKeyManager = new TransportKeyManagerImpl( db, transportCrypto, dbExecutor, scheduler, clock, transportId, maxLatency); - // The timestamp is at the start of rotation period 1000 - long timestamp = rotationPeriodLength * 1000; + // The timestamp is at the start of time period 1000 + long timestamp = timePeriodLength * 1000; assertEquals(keySetId, transportKeyManager.addContact(txn, contactId, - masterKey, timestamp, alice, false)); + rootKey, timestamp, alice, false)); // The keys are inactive so no stream context should be returned assertFalse(transportKeyManager.canSendOutgoingStreams(contactId)); assertNull(transportKeyManager.getStreamContext(txn, contactId)); @@ -488,12 +488,12 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { private void expectAddContactNoRotation(boolean alice, boolean active, TransportKeys transportKeys, Transaction txn) throws Exception { context.checking(new Expectations() {{ - oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey, + oneOf(transportCrypto).deriveTransportKeys(transportId, rootKey, 1000, alice, active); will(returnValue(transportKeys)); - // Get the current time (the start of rotation period 1000) + // Get the current time (the start of time period 1000) oneOf(clock).currentTimeMillis(); - will(returnValue(rotationPeriodLength * 1000)); + will(returnValue(timePeriodLength * 1000)); // Encode the tags (3 sets) for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) { exactly(3).of(transportCrypto).encodeTag( @@ -510,16 +510,16 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase { }}); } - private TransportKeys createTransportKeys(long rotationPeriod, + private TransportKeys createTransportKeys(long timePeriod, long streamCounter, boolean active) { IncomingKeys inPrev = new IncomingKeys(tagKey, headerKey, - rotationPeriod - 1); + timePeriod - 1); IncomingKeys inCurr = new IncomingKeys(tagKey, headerKey, - rotationPeriod); + timePeriod); IncomingKeys inNext = new IncomingKeys(tagKey, headerKey, - rotationPeriod + 1); + timePeriod + 1); OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey, - rotationPeriod, streamCounter, active); + timePeriod, streamCounter, active); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index 5639e848f..3a96c3eba 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -23,7 +23,7 @@ import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.transport.KeyManager; -import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.SessionId; @@ -430,7 +430,7 @@ class IntroduceeProtocolEngine s.getRemote().acceptTimestamp); if (timestamp == -1) throw new AssertionError(); - Map keys = null; + Map keys = null; try { contactManager .addContact(txn, s.getRemote().author, localAuthor.getId(), diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java index b952b3dc5..5a6967a90 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java @@ -8,7 +8,7 @@ import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.introduction.Role; @@ -17,9 +17,9 @@ import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_ACTIVATE; import static org.briarproject.briar.introduction.IntroduceeState.START; -import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; @Immutable @NotNullByDefault @@ -33,12 +33,12 @@ class IntroduceeSession extends Session @Nullable private final byte[] masterKey; @Nullable - private final Map transportKeys; + private final Map transportKeys; IntroduceeSession(SessionId sessionId, IntroduceeState state, long requestTimestamp, GroupId contactGroupId, Author introducer, Local local, Remote remote, @Nullable byte[] masterKey, - @Nullable Map transportKeys) { + @Nullable Map transportKeys) { super(sessionId, state, requestTimestamp); this.contactGroupId = contactGroupId; this.introducer = introducer; @@ -113,7 +113,8 @@ class IntroduceeSession extends Session } static IntroduceeSession awaitActivate(IntroduceeSession s, AuthMessage m, - Message sent, @Nullable Map transportKeys) { + Message sent, + @Nullable Map transportKeys) { Local local = new Local(s.local, sent.getId(), sent.getTimestamp()); Remote remote = new Remote(s.remote, m.getMessageId()); return new IntroduceeSession(s.getSessionId(), AWAIT_ACTIVATE, @@ -180,7 +181,7 @@ class IntroduceeSession extends Session } @Nullable - Map getTransportKeys() { + Map getTransportKeys() { return transportKeys; } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java index 37f7aa10f..79b77a149 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java @@ -41,7 +41,8 @@ interface IntroductionCrypto { /** * Derives a MAC key from the session's master key for Alice or Bob. * - * @param masterKey The key returned by {@link #deriveMasterKey(IntroduceeSession)} + * @param masterKey The key returned by + * {@link #deriveMasterKey(IntroduceeSession)} * @param alice true for Alice's MAC key, false for Bob's * @return The MAC key */ diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java index 9023f0d94..434608503 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java @@ -6,7 +6,7 @@ import org.briarproject.bramble.api.data.BdfEntry; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.briar.introduction.IntroduceeSession.Common; import org.briarproject.briar.introduction.IntroduceeSession.Local; import org.briarproject.briar.introduction.IntroduceeSession.Remote; @@ -143,10 +143,10 @@ class SessionEncoderImpl implements SessionEncoder { @Nullable private BdfDictionary encodeTransportKeys( - @Nullable Map keys) { + @Nullable Map keys) { if (keys == null) return null; BdfDictionary d = new BdfDictionary(); - for (Map.Entry e : keys.entrySet()) { + for (Map.Entry e : keys.entrySet()) { d.put(e.getKey().getString(), e.getValue().getInt()); } return d; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java index 52c12e547..471d8efa2 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java @@ -10,7 +10,7 @@ import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.introduction.Role; import org.briarproject.briar.introduction.IntroduceeSession.Local; @@ -110,7 +110,7 @@ class SessionParserImpl implements SessionParser { Local local = parseLocal(d.getDictionary(SESSION_KEY_LOCAL)); Remote remote = parseRemote(d.getDictionary(SESSION_KEY_REMOTE)); byte[] masterKey = d.getOptionalRaw(SESSION_KEY_MASTER_KEY); - Map transportKeys = parseTransportKeys( + Map transportKeys = parseTransportKeys( d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS)); return new IntroduceeSession(sessionId, state, requestTimestamp, introducerGroupId, introducer, local, remote, @@ -184,14 +184,13 @@ class SessionParserImpl implements SessionParser { } @Nullable - private Map parseTransportKeys( + private Map parseTransportKeys( @Nullable BdfDictionary d) throws FormatException { if (d == null) return null; - Map map = new HashMap<>(d.size()); + Map map = new HashMap<>(d.size()); for (String key : d.keySet()) { map.put(new TransportId(key), - new KeySetId(d.getLong(key).intValue()) - ); + new TransportKeySetId(d.getLong(key).intValue())); } return map; } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java index 9ddb00632..df29af3d7 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java @@ -10,7 +10,7 @@ import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.transport.TransportKeySetId; import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.introduction.IntroducerSession.Introducee; @@ -75,7 +75,8 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { getTransportPropertiesMap(3); private final Map remoteTransportProperties = getTransportPropertiesMap(3); - private final Map transportKeys = new HashMap<>(); + private final Map transportKeys = + new HashMap<>(); private final byte[] localMacKey = getRandomBytes(SecretKey.LENGTH); private final byte[] remoteMacKey = getRandomBytes(SecretKey.LENGTH); @@ -88,9 +89,9 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { sessionParser = new SessionParserImpl(clientHelper); author1 = getRealAuthor(authorFactory); author2 = getRealAuthor(authorFactory); - transportKeys.put(getTransportId(), new KeySetId(1)); - transportKeys.put(getTransportId(), new KeySetId(2)); - transportKeys.put(getTransportId(), new KeySetId(3)); + transportKeys.put(getTransportId(), new TransportKeySetId(1)); + transportKeys.put(getTransportId(), new TransportKeySetId(2)); + transportKeys.put(getTransportId(), new TransportKeySetId(3)); } @Test diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java index af0c4bea9..bbe1baaf3 100644 --- a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java @@ -56,7 +56,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { private final File testDir = getTestDirectory(); private final File aliceDir = new File(testDir, "alice"); private final File bobDir = new File(testDir, "bob"); - private final SecretKey master = getSecretKey(); + private final SecretKey rootKey = getSecretKey(); private final long timestamp = System.currentTimeMillis(); private SimplexMessagingIntegrationTestComponent alice, bob; @@ -110,7 +110,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase { lifecycleManager.waitForStartup(); // Add the other user as a contact ContactManager contactManager = device.getContactManager(); - return contactManager.addContact(remote, local.getId(), master, + return contactManager.addContact(remote, local.getId(), rootKey, timestamp, alice, true, true); }