From 54e812a7c8c08d8a6d3fb7c46c3f8bf3aeb76d18 Mon Sep 17 00:00:00 2001 From: Julian Dehm <goapunk@riseup.net> Date: Tue, 25 Sep 2018 20:03:25 +0200 Subject: [PATCH] first test --- .../properties/TransportPropertyManager.java | 4 + .../TransportPropertyManagerImpl.java | 16 +- .../MailboxIntroductionSucceededEvent.java | 22 + .../mailbox/AbstractIntroduceeSession.java | 4 +- .../briar/mailbox/AbstractProtocolEngine.java | 48 +- .../mailbox/IntroduceeAcceptMessage.java | 52 ++ .../mailbox/IntroduceeProtocolEngine.java | 86 ++- .../mailbox/IntroduceeRequestMessage.java | 9 +- .../briar/mailbox/IntroduceeSession.java | 41 +- .../briar/mailbox/MailboxAcceptMessage.java | 41 ++ .../briar/mailbox/MailboxAuthMessage.java | 15 +- .../mailbox/MailboxIntroductionCrypto.java | 14 +- .../MailboxIntroductionCryptoImpl.java | 22 +- .../MailboxIntroductionManagerImpl.java | 108 +++- .../mailbox/MailboxIntroductionValidator.java | 95 +++- .../briar/mailbox/MailboxMessageEncoder.java | 20 +- .../mailbox/MailboxMessageEncoderImpl.java | 44 +- .../briar/mailbox/MailboxMessageParser.java | 8 +- .../mailbox/MailboxMessageParserImpl.java | 54 +- .../briar/mailbox/MailboxProtocolEngine.java | 102 +++- .../briar/mailbox/MailboxSession.java | 63 +-- .../briar/mailbox/MailboxSessionParser.java | 3 + .../mailbox/MailboxSessionParserImpl.java | 22 +- .../briar/mailbox/MailboxState.java | 2 +- .../briar/mailbox/OwnerProtocolEngine.java | 63 ++- .../briar/mailbox/OwnerSession.java | 5 +- .../briar/mailbox/ProtocolEngine.java | 4 +- .../briarproject/briar/mailbox/Session.java | 4 - .../briar/forum/ForumManagerTest.java | 4 + .../MailboxIntroductionIntegrationTest.java | 29 +- mailbox-android/build.gradle | 1 + .../mailbox/android/AndroidComponent.java | 5 +- .../android/MailboxApplicationImpl.java | 2 + mailbox-core/build.gradle | 39 ++ .../briar/MailboxCoreEagerSingletons.java | 9 + .../briarproject/briar/MailboxCoreModule.java | 15 + .../briar/mailbox/AbortMessage.java | 27 + .../mailbox/AbstractIntroduceeSession.java | 157 ++++++ .../AbstractMailboxIntroductionMessage.java | 45 ++ .../briar/mailbox/AbstractProtocolEngine.java | 207 ++++++++ .../briar/mailbox/BdfIncomingMessageHook.java | 66 +++ .../briar/mailbox/ContactAcceptMessage.java | 52 ++ .../briar/mailbox/ContactRequestMessage.java | 42 ++ .../briar/mailbox/DeclineMessage.java | 28 + .../briar/mailbox/Introducee.java | 77 +++ .../mailbox/IntroduceeAcceptMessage.java | 52 ++ .../mailbox/IntroduceeProtocolEngine.java | 274 ++++++++++ .../mailbox/IntroduceeRequestMessage.java | 48 ++ .../briar/mailbox/IntroduceeSession.java | 151 ++++++ .../briar/mailbox/IntroduceeState.java | 36 ++ .../briar/mailbox/IntroductionConstants.java | 49 ++ .../briar/mailbox/MailboxAcceptMessage.java | 41 ++ .../briar/mailbox/MailboxAuthMessage.java | 51 ++ .../mailbox/MailboxIntroductionCrypto.java | 100 ++++ .../MailboxIntroductionCryptoImpl.java | 232 +++++++++ .../MailboxIntroductionManagerImpl.java | 490 ++++++++++++++++++ .../mailbox/MailboxIntroductionModule.java | 96 ++++ .../mailbox/MailboxIntroductionValidator.java | 248 +++++++++ .../briar/mailbox/MailboxMessageEncoder.java | 66 +++ .../mailbox/MailboxMessageEncoderImpl.java | 172 ++++++ .../briar/mailbox/MailboxMessageParser.java | 40 ++ .../mailbox/MailboxMessageParserImpl.java | 156 ++++++ .../briar/mailbox/MailboxProtocolEngine.java | 278 ++++++++++ .../briar/mailbox/MailboxSession.java | 139 +++++ .../briar/mailbox/MailboxSessionEncoder.java | 18 + .../mailbox/MailboxSessionEncoderImpl.java | 157 ++++++ .../briar/mailbox/MailboxSessionParser.java | 28 + .../mailbox/MailboxSessionParserImpl.java | 230 ++++++++ .../briar/mailbox/MailboxState.java | 37 ++ .../briar/mailbox/MessageMetadata.java | 55 ++ .../briar/mailbox/MessageType.java | 30 ++ .../briar/mailbox/OwnerProtocolEngine.java | 218 ++++++++ .../briar/mailbox/OwnerSession.java | 52 ++ .../briar/mailbox/OwnerState.java | 36 ++ .../briar/mailbox/PeerSession.java | 25 + .../briar/mailbox/ProtocolEngine.java | 46 ++ .../briar/mailbox/RequestMessage.java | 28 + .../briarproject/briar/mailbox/Session.java | 44 ++ .../org/briarproject/briar/mailbox/State.java | 7 + mailbox-core/witness.gradle | 4 + settings.gradle | 1 + 81 files changed, 5300 insertions(+), 211 deletions(-) create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionSucceededEvent.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeAcceptMessage.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java create mode 100644 mailbox-core/build.gradle create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/MailboxCoreEagerSingletons.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/MailboxCoreModule.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbortMessage.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractMailboxIntroductionMessage.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/BdfIncomingMessageHook.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/ContactAcceptMessage.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/ContactRequestMessage.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/DeclineMessage.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/Introducee.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeAcceptMessage.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeState.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroductionConstants.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionModule.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxState.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MessageMetadata.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/MessageType.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerState.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/PeerSession.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/RequestMessage.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/Session.java create mode 100644 mailbox-core/src/main/java/org/briarproject/briar/mailbox/State.java create mode 100644 mailbox-core/witness.gradle diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java index 1c64f5b6d..164d1f5a2 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java @@ -35,6 +35,10 @@ public interface TransportPropertyManager { void addRemoteProperties(Transaction txn, ContactId c, Map<TransportId, TransportProperties> props) throws DbException; + Map<TransportId, TransportProperties> getLocalAnonymizedProperties( + Transaction txn) + throws DbException; + /** * Returns the local transport properties for all transports. */ diff --git a/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java index 4b058a6da..7851663bc 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java @@ -108,7 +108,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, @Override public void onClientVisibilityChanging(Transaction txn, Contact c, Visibility v) throws DbException { - if(!getApplicableContactTypes().contains(c.getType())) return; + if (!getApplicableContactTypes().contains(c.getType())) return; // Apply the client's visibility to the contact group Group g = getContactGroup(c); db.setGroupVisibility(txn, c.getId(), g.getId(), v); @@ -154,7 +154,19 @@ class TransportPropertyManagerImpl implements TransportPropertyManager, throws DbException { Map<TransportId, TransportProperties> properties = getLocalProperties(); for (Entry e : properties.entrySet()) { - e.setValue(Collections.EMPTY_MAP); + e.setValue(new TransportProperties(Collections.EMPTY_MAP)); + } + return properties; + } + + @Override + public Map<TransportId, TransportProperties> getLocalAnonymizedProperties( + Transaction txn) + throws DbException { + Map<TransportId, TransportProperties> properties = + getLocalProperties(txn); + for (Entry e : properties.entrySet()) { + e.setValue(new TransportProperties(Collections.EMPTY_MAP)); } return properties; } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionSucceededEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionSucceededEvent.java new file mode 100644 index 000000000..e23ee8a69 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/mailbox/event/MailboxIntroductionSucceededEvent.java @@ -0,0 +1,22 @@ +package org.briarproject.briar.api.mailbox.event; + +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class MailboxIntroductionSucceededEvent extends Event { + + private final Contact contact; + + public MailboxIntroductionSucceededEvent(Contact contact) { + this.contact = contact; + } + + public Contact getContact() { + return contact; + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java index 76ece2095..6bf182a55 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java @@ -16,7 +16,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -abstract class AbstractIntroduceeSession extends Session<IntroduceeState> +abstract class AbstractIntroduceeSession<S extends State> extends Session<S> implements PeerSession { final GroupId contactGroupId; @@ -28,7 +28,7 @@ abstract class AbstractIntroduceeSession extends Session<IntroduceeState> @Nullable final Map<TransportId, KeySetId> transportKeys; - AbstractIntroduceeSession(SessionId sessionId, IntroduceeState state, + AbstractIntroduceeSession(SessionId sessionId, S state, long requestTimestamp, GroupId contactGroupId, Author introducer, Local local, Remote remote, @Nullable byte[] masterKey, @Nullable Map<TransportId, KeySetId> transportKeys, diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java index 5397d7b7e..57753e1c2 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java @@ -11,12 +11,18 @@ import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.bramble.api.properties.TransportPropertyManager; 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.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; +import java.util.Map; + import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -25,6 +31,7 @@ import static org.briarproject.briar.mailbox.MessageType.DECLINE; import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_ACCEPT; import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_REQUEST; import static org.briarproject.briar.mailbox.MessageType.MAILBOX_ACCEPT; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_AUTH; import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST; @Immutable @@ -42,6 +49,8 @@ abstract class AbstractProtocolEngine<S extends Session> protected final MailboxMessageEncoder messageEncoder; protected final Clock clock; protected final MailboxIntroductionCrypto crypto; + protected final KeyManager keyManager; + protected final TransportPropertyManager transportPropertyManager; AbstractProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, ContactManager contactManager, @@ -49,7 +58,9 @@ abstract class AbstractProtocolEngine<S extends Session> MessageTracker messageTracker, IdentityManager identityManager, MailboxMessageParser messageParser, MailboxMessageEncoder messageEncoder, Clock clock, - MailboxIntroductionCrypto crypto) { + MailboxIntroductionCrypto crypto, + KeyManager keyManager, + TransportPropertyManager transportPropertyManager) { this.db = db; this.clientHelper = clientHelper; this.contactManager = contactManager; @@ -60,6 +71,8 @@ abstract class AbstractProtocolEngine<S extends Session> this.messageEncoder = messageEncoder; this.clock = clock; this.crypto = crypto; + this.keyManager = keyManager; + this.transportPropertyManager = transportPropertyManager; } Message sendMailboxRequestMessage(Transaction txn, PeerSession s, @@ -78,8 +91,9 @@ abstract class AbstractProtocolEngine<S extends Session> byte[] ephemeralPublicKey, long acceptTimestamp, long messageCounter) throws DbException { Message m = messageEncoder - .encodeIntroduceeRequestMessage(s.getContactGroupId(), + .encodeMailboxAcceptMessage(s.getContactGroupId(), timestamp, s.getLastLocalMessageId(), s.getSessionId(), + MAILBOX_ACCEPT, ephemeralPublicKey, acceptTimestamp, messageCounter); @@ -89,12 +103,14 @@ abstract class AbstractProtocolEngine<S extends Session> } Message sendIntroduceeRequestMessage(Transaction txn, PeerSession s, - long timestamp, + long timestamp, Author author, byte[] ephemeralPublicKey, long acceptTimestamp, long messageCounter) throws DbException { Message m = messageEncoder .encodeIntroduceeRequestMessage(s.getContactGroupId(), timestamp, s.getLastLocalMessageId(), s.getSessionId(), + INTRODUCEE_REQUEST, + author, ephemeralPublicKey, acceptTimestamp, messageCounter); @@ -104,33 +120,33 @@ abstract class AbstractProtocolEngine<S extends Session> } Message sendIntroduceeResponseMessage(Transaction txn, PeerSession s, + MessageId previousMessage, long timestamp, byte[] ephemeralPublicKey, byte[] mac, - byte[] signature, long messageCounter) throws DbException { + byte[] signature, long acceptTimestamp, long messageCounter) + throws DbException { Message m = messageEncoder .encodeIntroduceeAcceptMessage(s.getContactGroupId(), timestamp, - s.getLastLocalMessageId(), s.getSessionId(), - ephemeralPublicKey, mac, signature, messageCounter); + previousMessage, s.getSessionId(), + ephemeralPublicKey, mac, signature, acceptTimestamp, + messageCounter); sendMessage(txn, INTRODUCEE_ACCEPT, s.getSessionId(), m, messageCounter); return m; } - /* - Message sendContactAcceptMessage(Transaction txn, PeerSession s, + Message sendMailboxAuthMessage(Transaction txn, PeerSession s, long timestamp, - byte[] ephemeralPublicKey, long acceptTimestamp, Map<TransportId, TransportProperties> transportProperties, - boolean visible) throws DbException { + byte[] mac, byte[] signature, long messageCounter) + throws DbException { Message m = messageEncoder - .encodeAcceptMessage(s.getContactGroupId(), timestamp, - s.getLastLocalMessageId(), s.getSessionId(), - ephemeralPublicKey, acceptTimestamp, - transportProperties); - sendMessage(txn, CONTACT_ACCEPT, s.getSessionId(), m); + .encodeMailboxAuthMessage(s.getContactGroupId(), timestamp, + s.getLastRemoteMessageId(), s.getSessionId(), + transportProperties, mac, signature, messageCounter); + sendMessage(txn, MAILBOX_AUTH, s.getSessionId(), m, messageCounter); return m; } - */ /* Message sendMailboxInfotMessage(Transaction txn, PeerSession s, diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeAcceptMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeAcceptMessage.java new file mode 100644 index 000000000..ebb481692 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeAcceptMessage.java @@ -0,0 +1,52 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class IntroduceeAcceptMessage extends AbstractMailboxIntroductionMessage { + + private final SessionId sessionId; + private final byte[] ephemeralPublicKey; + private final long acceptTimestamp; + private final byte[] mac; + private final byte[] signature; + + protected IntroduceeAcceptMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, byte[] ephemeralPublicKey, byte[] mac, + byte[] signature, long acceptTimestamp) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + this.ephemeralPublicKey = ephemeralPublicKey; + this.acceptTimestamp = acceptTimestamp; + this.mac = mac; + this.signature = signature; + } + + public SessionId getSessionId() { + return sessionId; + } + + public byte[] getEphemeralPublicKey() { + return ephemeralPublicKey; + } + + public long getAcceptTimestamp() { + return acceptTimestamp; + } + + public byte[] getMac() { + return mac; + } + + public byte[] getSignature() { + return signature; + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java index 8ac793092..c2ad06076 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java @@ -3,6 +3,7 @@ package org.briarproject.briar.mailbox; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.SecretKey; @@ -12,14 +13,20 @@ import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportPropertyManager; 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.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.mailbox.event.MailboxIntroductionRequestReceivedEvent; +import org.briarproject.briar.api.mailbox.event.MailboxIntroductionSucceededEvent; import java.security.GeneralSecurityException; +import java.util.Map; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -28,6 +35,8 @@ import javax.inject.Inject; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.mailbox.IntroduceeState.AWAIT_AUTH; +import static org.briarproject.briar.mailbox.IntroduceeState.LOCAL_ACCEPTED; +import static org.briarproject.briar.mailbox.IntroduceeState.MAILBOX_ADDED; class IntroduceeProtocolEngine extends AbstractProtocolEngine<IntroduceeSession> { @@ -42,10 +51,11 @@ class IntroduceeProtocolEngine MessageTracker messageTracker, IdentityManager identityManager, MailboxMessageParser messageParser, MailboxMessageEncoder messageEncoder, Clock clock, - MailboxIntroductionCrypto crypto) { + MailboxIntroductionCrypto crypto, KeyManager keyManager, + TransportPropertyManager transportPropertyManager) { super(db, clientHelper, contactManager, contactGroupFactory, messageTracker, identityManager, messageParser, messageEncoder, - clock, crypto); + clock, crypto, keyManager, transportPropertyManager); } @Override @@ -75,7 +85,7 @@ class IntroduceeProtocolEngine @Override public IntroduceeSession onMailboxAcceptMessage(Transaction txn, - IntroduceeSession session, IntroduceeRequestMessage m) + IntroduceeSession session, MailboxAcceptMessage m) throws DbException, FormatException { throw new UnsupportedOperationException(); } @@ -120,22 +130,29 @@ class IntroduceeProtocolEngine long localTimestamp = clock.currentTimeMillis(); try { SecretKey secretKey = crypto.deriveMasterKey(publicKey, privateKey, - m.getEphemeralPublicKey()); - SecretKey aliceMacKey = crypto.deriveMacKey(secretKey, false); + m.getEphemeralPublicKey(), false); + SecretKey aliceMacKey = crypto.deriveMacKey(secretKey, true); SecretKey bobMacKey = crypto.deriveMacKey(secretKey, false); + + s = IntroduceeSession + .addLocalAccept(s, LOCAL_ACCEPTED, m, publicKey, privateKey, + localTimestamp, secretKey.getBytes(), + aliceMacKey.getBytes(), bobMacKey.getBytes()); + byte[] mac = crypto.authMac(bobMacKey, s, localAuthor.getId()); byte[] signature = crypto.sign(bobMacKey, localAuthor.getPrivateKey()); + // Send ephemeral public key and timestamp back Message reply = - sendIntroduceeResponseMessage(txn, s, localTimestamp, - publicKey, mac, signature, s.getMessageCounter()); + sendIntroduceeResponseMessage(txn, s, m.getMessageId(), + localTimestamp, + publicKey, mac, signature, localTimestamp, + s.getMessageCounter()); //TODO: Check for reasons to decline and if any, move to LOCAL_DECLINE // Move to the AWAIT_REMOTE_RESPONSE state return IntroduceeSession - .addLocalAuth(s, AWAIT_AUTH, reply, secretKey, aliceMacKey, - bobMacKey, - localTimestamp); + .addLocalAuth(s, AWAIT_AUTH, reply); } catch (GeneralSecurityException e) { logException(LOG, WARNING, e); return abort(txn, s); @@ -177,9 +194,48 @@ class IntroduceeProtocolEngine @Override public IntroduceeSession onAuthMessage(Transaction txn, - IntroduceeSession session, + IntroduceeSession s, MailboxAuthMessage m) throws DbException, FormatException { - return null; + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s.getLastLocalMessageId(), + m.getPreviousMessageId())) + return abort(txn, s); + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + try { + crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId()); + crypto.verifySignature(m.getSignature(), s); + long timestamp = Math.min(s.getLocal().acceptTimestamp, + s.getRemote().acceptTimestamp); + if (timestamp == -1) throw new AssertionError(); + contactManager + .addContact(txn, s.getRemote().author, localAuthor.getId(), + false, true); + // Only add transport properties and keys when the contact was added + // This will be changed once we have a way to reset state for peers + // that were contacts already at some point in the past. + Contact c = contactManager + .getContact(txn, s.getRemote().author.getId(), + localAuthor.getId()); + // add the keys to the new contact + //noinspection ConstantConditions + Map<TransportId, KeySetId> keys = keyManager + .addContact(txn, c.getId(), new SecretKey(s.masterKey), + timestamp, s.getLocal().alice, true); + // add signed transport properties for the contact + //noinspection ConstantConditions + transportPropertyManager.addRemoteProperties(txn, c.getId(), + m.getTransportProperties()); + // Broadcast MailboxIntroductionSucceededEvent, because contact got added + MailboxIntroductionSucceededEvent e = + new MailboxIntroductionSucceededEvent(c); + txn.attach(e); + } catch (GeneralSecurityException e) { + logException(LOG, WARNING, e); + return abort(txn, s); + } + return IntroduceeSession + .clear(s, MAILBOX_ADDED, s.getLastLocalMessageId(), + clock.currentTimeMillis(), m.getMessageId()); } @Override @@ -189,6 +245,12 @@ class IntroduceeProtocolEngine return null; } + @Override + public IntroduceeSession onIntroduceeAcceptMessage(Transaction txn, + IntroduceeSession session, IntroduceeAcceptMessage acceptMessage) { + throw new UnsupportedOperationException(); + } + private boolean isInvalidDependency(AbstractIntroduceeSession s, @Nullable MessageId dependency) { diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java index 88146af1d..3e5ea3d3e 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java @@ -1,5 +1,6 @@ package org.briarproject.briar.mailbox; +import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; @@ -13,15 +14,17 @@ import javax.annotation.concurrent.Immutable; class IntroduceeRequestMessage extends AbstractMailboxIntroductionMessage { private final SessionId sessionId; + private final Author author; private final byte[] ephemeralPublicKey; private final long acceptTimestamp; protected IntroduceeRequestMessage(MessageId messageId, GroupId groupId, long timestamp, @Nullable MessageId previousMessageId, - SessionId sessionId, byte[] ephemeralPublicKey, + SessionId sessionId, Author author, byte[] ephemeralPublicKey, long acceptTimestamp) { super(messageId, groupId, timestamp, previousMessageId); this.sessionId = sessionId; + this.author = author; this.ephemeralPublicKey = ephemeralPublicKey; this.acceptTimestamp = acceptTimestamp; } @@ -30,6 +33,10 @@ class IntroduceeRequestMessage extends AbstractMailboxIntroductionMessage { return sessionId; } + public Author getAuthor() { + return author; + } + public byte[] getEphemeralPublicKey() { return ephemeralPublicKey; } diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java index 825ada662..29270beea 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java @@ -1,6 +1,5 @@ package org.briarproject.briar.mailbox; -import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; @@ -22,7 +21,7 @@ import static org.briarproject.briar.mailbox.IntroduceeState.START; @Immutable @NotNullByDefault -class IntroduceeSession extends AbstractIntroduceeSession { +class IntroduceeSession extends AbstractIntroduceeSession<IntroduceeState> { IntroduceeSession(SessionId sessionId, IntroduceeState state, @@ -46,34 +45,34 @@ class IntroduceeSession extends AbstractIntroduceeSession { } static IntroduceeSession addLocalAccept(IntroduceeSession s, - IntroduceeState state, Message m, byte[] ephemeralPublicKey, - byte[] ephemeralPrivateKey, long acceptTimestamp) { - Local local = new Local(false, m.getId(), m.getTimestamp(), - ephemeralPublicKey, ephemeralPrivateKey, acceptTimestamp, null); + IntroduceeState state, IntroduceeRequestMessage m, + byte[] ephemeralPublicKey, + byte[] ephemeralPrivateKey, long acceptTimestamp, byte[] masterKey, + byte[] aliceMackey, byte[] bobMacKey) { + Local local = new Local(false, s.local.lastMessageId, + s.local.lastMessageTimestamp, + ephemeralPublicKey, ephemeralPrivateKey, acceptTimestamp, + bobMacKey); + Remote remote = new Remote(true, m.getAuthor(), m.getMessageId(), + m.getEphemeralPublicKey(), null, m.getAcceptTimestamp(), + aliceMackey); return new IntroduceeSession(s.getSessionId(), state, m.getTimestamp(), - s.contactGroupId, s.introducer, local, s.remote, s.masterKey, - s.transportKeys, s.getMessageCounter() + 1); + s.contactGroupId, s.introducer, local, remote, masterKey, + s.transportKeys, s.getMessageCounter()); } static IntroduceeSession addLocalAuth(IntroduceeSession s, IntroduceeState state, - Message m, SecretKey masterKey, - SecretKey aliceMacKey, - SecretKey bobMacKey, long acceptTimestamp) { + Message m) { // add mac key and sent message Local local = new Local(false, m.getId(), m.getTimestamp(), - null, null, - acceptTimestamp, bobMacKey.getBytes()); - // just add the mac key - Remote remote = new Remote(true, s.remote.author, - s.remote.lastMessageId, null, null, - s.remote.acceptTimestamp, - aliceMacKey.getBytes()); - // add master key + s.local.ephemeralPublicKey, s.local.ephemeralPrivateKey, + s.local.acceptTimestamp, s.local.macKey); + return new IntroduceeSession(s.getSessionId(), state, s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, - remote, masterKey.getBytes(), s.transportKeys, - s.getMessageCounter() + 1); + s.remote, s.masterKey, s.transportKeys, + s.getMessageCounter()); } static IntroduceeSession awaitAuth(IntroduceeSession s, diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java new file mode 100644 index 000000000..6b740de82 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java @@ -0,0 +1,41 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class MailboxAcceptMessage extends AbstractMailboxIntroductionMessage { + + private final SessionId sessionId; + private final byte[] ephemeralPublicKey; + private final long acceptTimestamp; + + protected MailboxAcceptMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, byte[] ephemeralPublicKey, + long acceptTimestamp) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + this.ephemeralPublicKey = ephemeralPublicKey; + this.acceptTimestamp = acceptTimestamp; + } + + public SessionId getSessionId() { + return sessionId; + } + + public byte[] getEphemeralPublicKey() { + return ephemeralPublicKey; + } + + public long getAcceptTimestamp() { + return acceptTimestamp; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java index da930637d..b66882a70 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java @@ -17,22 +17,17 @@ import javax.annotation.concurrent.Immutable; class MailboxAuthMessage extends AbstractMailboxIntroductionMessage { private final SessionId sessionId; - private final byte[] ephemeralPublicKey; - private final long acceptTimestamp; private final Map<TransportId, TransportProperties> transportProperties; private final byte[] mac; private final byte[] signature; protected MailboxAuthMessage(MessageId messageId, GroupId groupId, long timestamp, @Nullable MessageId previousMessageId, - SessionId sessionId, byte[] ephemeralPublicKey, - long acceptTimestamp, + SessionId sessionId, Map<TransportId, TransportProperties> transportProperties, byte[] mac, byte[] signature) { super(messageId, groupId, timestamp, previousMessageId); this.sessionId = sessionId; - this.ephemeralPublicKey = ephemeralPublicKey; - this.acceptTimestamp = acceptTimestamp; this.transportProperties = transportProperties; this.mac = mac; this.signature = signature; @@ -42,14 +37,6 @@ class MailboxAuthMessage extends AbstractMailboxIntroductionMessage { return sessionId; } - public byte[] getEphemeralPublicKey() { - return ephemeralPublicKey; - } - - public long getAcceptTimestamp() { - return acceptTimestamp; - } - public byte[] getMac() { return mac; } diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java index ea4d38f63..21a39bfbb 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java @@ -15,15 +15,8 @@ interface MailboxIntroductionCrypto { * Returns the {@link SessionId} based on the introducer * and the two introducees. */ - SessionId getSessionId(Author introducer, Author local, Author remote); - - /** - * Returns true if the local author is alice - * <p> - * Alice is the Author whose unique ID has the lower ID, - * comparing the IDs as byte strings. - */ - boolean isAlice(AuthorId local, AuthorId remote); + SessionId getSessionId(Author introducer, Author local, Author remote, + boolean isAlice); /** * Generates an agreement key pair. @@ -40,7 +33,8 @@ interface MailboxIntroductionCrypto { @SuppressWarnings("ConstantConditions") SecretKey deriveMasterKey(byte[] ephemeralPublicKey, - byte[] ephemeralPrivateKey, byte[] remoteEphemeralPublicKey) + byte[] ephemeralPrivateKey, byte[] remoteEphemeralPublicKey, + boolean alice) throws GeneralSecurityException; /** diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java index debe6e83f..0de274163 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java @@ -49,8 +49,7 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto { @Override public SessionId getSessionId(Author introducer, Author local, - Author remote) { - boolean isAlice = isAlice(local.getId(), remote.getId()); + Author remote, boolean isAlice) { byte[] hash = crypto.hash( LABEL_SESSION_ID, introducer.getId().getBytes(), @@ -65,11 +64,6 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto { return crypto.generateAgreementKeyPair(); } - @Override - public boolean isAlice(AuthorId local, AuthorId remote) { - return local.compareTo(remote) < 0; - } - @Override @SuppressWarnings("ConstantConditions") public SecretKey deriveMasterKey(AbstractIntroduceeSession s) @@ -83,19 +77,7 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto { } @Override - @SuppressWarnings("ConstantConditions") - public SecretKey deriveMasterKey(byte[] ephemeralPublicKey, - byte[] ephemeralPrivateKey, byte[] remoteEphemeralPublicKey) - throws GeneralSecurityException { - return deriveMasterKey( - ephemeralPublicKey, - ephemeralPrivateKey, - remoteEphemeralPublicKey, - false - ); - } - - SecretKey deriveMasterKey(byte[] publicKey, byte[] privateKey, + public SecretKey deriveMasterKey(byte[] publicKey, byte[] privateKey, byte[] remotePublicKey, boolean alice) throws GeneralSecurityException { KeyParser kp = crypto.getAgreementKeyParser(); diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java index 3a81dd373..4192afbe8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java @@ -17,11 +17,14 @@ import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.mailbox.MailboxInfo; import org.briarproject.bramble.api.sync.Client; import org.briarproject.bramble.api.sync.Group; 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.system.Clock; import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.mailbox.MailboxIntroductionManager; @@ -31,6 +34,7 @@ import org.briarproject.briar.client.BdfIncomingMessageHook; import java.util.Arrays; import java.util.Collection; import java.util.Map; +import java.util.concurrent.Executor; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -38,12 +42,14 @@ import javax.inject.Inject; import static org.briarproject.bramble.api.contact.ContactManager.ContactHook; import static org.briarproject.bramble.api.contact.ContactType.MAILBOX_OWNER; +import static org.briarproject.bramble.api.contact.ContactType.PRIVATE_MAILBOX; import static org.briarproject.bramble.api.contact.ContactType.values; import static org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import static org.briarproject.briar.api.mailbox.Role.INTRODUCEE; import static org.briarproject.briar.api.mailbox.Role.MAILBOX; import static org.briarproject.briar.api.mailbox.Role.OWNER; import static org.briarproject.briar.mailbox.IntroductionConstants.GROUP_KEY_CONTACT_ID; +import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_REQUEST; import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST; class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook @@ -52,6 +58,7 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook private static final Logger LOG = Logger.getLogger(MailboxIntroductionManagerImpl.class.getName()); + private final Executor ioExecutor; private final ClientVersioningManager clientVersioningManager; private final ContactGroupFactory contactGroupFactory; private final ContactManager contactManager; @@ -59,15 +66,18 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook // private final SessionEncoder sessionEncoder; private final MailboxSessionParser sessionParser; private final OwnerProtocolEngine ownerProtocolEngine; + private final IntroduceeProtocolEngine introduceeProtocolEngine; private final MailboxSessionEncoder sessionEncoder; private final MailboxIntroductionCrypto crypto; private final MailboxProtocolEngine mailboxProtocolEngine; private final IdentityManager identityManager; + private final Clock clock; private final Group localGroup; @Inject - MailboxIntroductionManagerImpl(DatabaseComponent db, + MailboxIntroductionManagerImpl(@IoExecutor Executor ioExecutor, + DatabaseComponent db, ClientHelper clientHelper, ClientVersioningManager clientVersioningManager, MetadataParser metadataParser, @@ -75,23 +85,28 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook ContactManager contactManager, MailboxMessageParser messageParser, MailboxSessionParser sessionParser, OwnerProtocolEngine ownerProtocolEngine, + IntroduceeProtocolEngine introduceeProtocolEngine, MailboxSessionEncoder sessionEncoder, MailboxIntroductionCrypto crypto, MailboxProtocolEngine mailboxProtocolEngine, - IdentityManager identityManager) { + IdentityManager identityManager, + Clock clock) { super(db, clientHelper, metadataParser); + this.ioExecutor = ioExecutor; this.clientVersioningManager = clientVersioningManager; this.contactGroupFactory = contactGroupFactory; this.contactManager = contactManager; this.messageParser = messageParser; this.sessionParser = sessionParser; this.ownerProtocolEngine = ownerProtocolEngine; + this.introduceeProtocolEngine = introduceeProtocolEngine; this.sessionEncoder = sessionEncoder; this.crypto = crypto; this.mailboxProtocolEngine = mailboxProtocolEngine; this.identityManager = identityManager; this.localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, MAJOR_VERSION); + this.clock = clock; } @Override @@ -170,24 +185,19 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook MessageMetadata meta = messageParser.parseMetadata(bdfMeta); // Look up the session, if there is one SessionId sessionId = meta.getSessionId(); - MailboxSession newMailboxSession = null; - if (sessionId == null) { - if (meta.getMessageType() != MAILBOX_REQUEST) - throw new AssertionError(); - newMailboxSession = createNewMailboxSession(txn, m, body); - sessionId = newMailboxSession.getSessionId(); - } + IntroduceeSession newMailboxSession = null; + if (sessionId == null) new AssertionError(); StoredSession ss = getSession(txn, sessionId); // Handle the message Session session; MessageId storageId; if (ss == null) { - if (meta.getMessageType() != MAILBOX_REQUEST) - throw new FormatException(); + if (meta.getMessageType() == INTRODUCEE_REQUEST) + newMailboxSession = createNewIntroduceeSession(txn, m, body); if (newMailboxSession == null) throw new AssertionError(); storageId = createStorageId(txn); session = handleMessage(txn, m, body, meta.getMessageType(), - newMailboxSession, mailboxProtocolEngine); + newMailboxSession, introduceeProtocolEngine); } else { storageId = ss.storageId; long messageCounter = @@ -203,8 +213,8 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook ownerProtocolEngine); } else if (role == INTRODUCEE) { session = handleMessage(txn, m, body, meta.getMessageType(), - sessionParser.parseMailboxSession(m.getGroupId(), - ss.bdfSession), mailboxProtocolEngine); + sessionParser.parseIntroduceeSession(m.getGroupId(), + ss.bdfSession), introduceeProtocolEngine); } else throw new AssertionError(); } // Store the updated session @@ -219,12 +229,27 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook Author local = identityManager.getLocalAuthor(txn); Author remote = messageParser.parseRequestMessage(m, body).getAuthor(); if (local.equals(remote)) throw new FormatException(); - SessionId sessionId = crypto.getSessionId(owner, local, remote); - boolean alice = crypto.isAlice(local.getId(), remote.getId()); + SessionId sessionId = crypto.getSessionId(owner, local, remote, true); return MailboxSession - .getInitial(m.getGroupId(), sessionId, owner, alice, remote); + .getInitial(m.getGroupId(), sessionId, owner, true, remote); } + private IntroduceeSession createNewIntroduceeSession(Transaction txn, + Message m, + BdfList body) + throws DbException, FormatException { + ContactId ownerId = getContactId(txn, m.getGroupId()); + Author owner = db.getContact(txn, ownerId).getAuthor(); + Author local = identityManager.getLocalAuthor(txn); + Author remote = messageParser.parseIntroduceeRequestMessage(m, body) + .getAuthor(); + if (local.equals(remote)) throw new FormatException(); + SessionId sessionId = crypto.getSessionId(owner, local, remote, false); + return IntroduceeSession + .getInitial(m.getGroupId(), sessionId, owner, false, remote); + } + + /* private IntroduceeSession createNewIntroduceeSession(Transaction txn, Message m, BdfList body) throws DbException, FormatException { @@ -252,15 +277,27 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook return engine.onRequestMessage(txn, session, request); } case MAILBOX_ACCEPT: { - IntroduceeRequestMessage acceptMessage = + MailboxAcceptMessage acceptMessage = messageParser.parseMailboxAcceptMessage(m, body); return engine .onMailboxAcceptMessage(txn, session, acceptMessage); } - case INTRODUCEE_REQUEST: + case INTRODUCEE_REQUEST: { IntroduceeRequestMessage acceptMessage = - messageParser.parseMailboxAcceptMessage(m, body); - return engine.onIntroduceeRequestMessage(txn, session, acceptMessage); + messageParser.parseIntroduceeRequestMessage(m, body); + return engine.onIntroduceeRequestMessage(txn, session, + acceptMessage); + } + case INTRODUCEE_ACCEPT: { + IntroduceeAcceptMessage acceptMessage = + messageParser.parseIntroduceeAcceptMessage(m, body); + return engine + .onIntroduceeAcceptMessage(txn, session, acceptMessage); + } + case MAILBOX_AUTH: + MailboxAuthMessage authMessage = + messageParser.parseMailboxAuthMessage(m, body); + return engine.onAuthMessage(txn, session, authMessage); case ABORT: { AbortMessage abort = messageParser.parseAbortMessage(m, body); return engine.onAbortMessage(txn, session, abort); @@ -279,14 +316,15 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook public void addingContact(Transaction txn, Contact c) throws DbException { switch (c.getType()) { case PRIVATE_MAILBOX: + break; case MAILBOX_OWNER: + break; case CONTACT: contactAdded(txn, c); break; default: return; } - } @Override @@ -306,7 +344,23 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook } catch (FormatException e) { throw new AssertionError(e); } - LOG.info("Contact added"); + Collection<Contact> pm = db.getContactsByType(txn, PRIVATE_MAILBOX); + if (pm.isEmpty()) return; + Collection<MailboxInfo> mailboxes = db.getContactMailboxes(txn); + for (MailboxInfo mailboxInfo : mailboxes) { + if (mailboxInfo.getContactId().equals(c.getId())) return; + } + ioExecutor.execute( + () -> { + try { + makeIntroduction((PrivateMailbox) pm.iterator().next(), + c, + clock.currentTimeMillis()); + } catch (DbException e) { + LOG.warning( + "Mailbox introduction failed: " + e.toString()); + } + }); } @Override @@ -338,7 +392,7 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook Author introducer = identityManager.getLocalAuthor(txn); SessionId sessionId = crypto.getSessionId(introducer, privateMailbox.getAuthor(), - contact.getAuthor()); + contact.getAuthor(), true); StoredSession ss = getSession(txn, sessionId); // Create or parse the session OwnerSession session; @@ -375,9 +429,11 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook BdfDictionary d; if (session.getRole() == OWNER) { d = sessionEncoder.encodeIntroducerSession((OwnerSession) session); - } else if (session.getRole() == MAILBOX) { + } else if (session.getRole() == MAILBOX || + session.getRole() == INTRODUCEE) { d = sessionEncoder - .encodeIntroduceeSession((AbstractIntroduceeSession) session); + .encodeIntroduceeSession( + (AbstractIntroduceeSession) session); } else { throw new AssertionError(); } diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java index 144fd6d88..8725fc9f2 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java @@ -22,8 +22,11 @@ import javax.annotation.concurrent.Immutable; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkSize; +import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_ACCEPT; +import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_REQUEST; import static org.briarproject.briar.mailbox.MessageType.MAILBOX_ACCEPT; import static org.briarproject.briar.mailbox.MessageType.MAILBOX_AUTH; @@ -51,8 +54,12 @@ class MailboxIntroductionValidator extends BdfMessageValidator { return validateRequestMessage(m, body); case MAILBOX_ACCEPT: return validateAcceptMessage(m, body); + case INTRODUCEE_REQUEST: + return validateIntroduceeRequestMessage(m, body); case MAILBOX_AUTH: return validateAuthMessage(m, body); + case INTRODUCEE_ACCEPT: + return validateIntroduceeAcceptMessage(m, body); case DECLINE: case ABORT: return validateOtherMessage(type, m, body); @@ -111,9 +118,82 @@ class MailboxIntroductionValidator extends BdfMessageValidator { } } + private BdfMessageContext validateIntroduceeRequestMessage(Message m, + BdfList body) + throws FormatException { + checkSize(body, 7); + + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); + + BdfList authorList = body.getList(2); + clientHelper.parseAndValidateAuthor(authorList); + + byte[] previousMessageId = body.getOptionalRaw(3); + checkLength(previousMessageId, UniqueId.LENGTH); + + byte[] ephemeralPublicKey = body.getRaw(4); + checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH); + + long timestamp = body.getLong(5); + if (timestamp < 0) throw new FormatException(); + + long messageCounter = body.getLong(6); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(INTRODUCEE_REQUEST, sessionId, m.getTimestamp(), + false, messageCounter); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + } + + private BdfMessageContext validateIntroduceeAcceptMessage(Message m, + BdfList body) + throws FormatException { + checkSize(body, 8); + + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); + + byte[] previousMessageId = body.getOptionalRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + byte[] ephemeralPublicKey = body.getRaw(3); + checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH); + + byte[] mac = body.getRaw(4); + checkLength(mac, MAC_BYTES); + + byte[] signature = body.getRaw(5); + checkLength(signature, 1, MAX_SIGNATURE_LENGTH); + + long timestamp = body.getLong(6); + if (timestamp < 0) throw new FormatException(); + + long messageCounter = body.getLong(7); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(INTRODUCEE_ACCEPT, sessionId, m.getTimestamp(), + false, messageCounter); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + } + private BdfMessageContext validateAuthMessage(Message m, BdfList body) throws FormatException { - checkSize(body, 5); + checkSize(body, 7); byte[] sessionIdBytes = body.getRaw(1); checkLength(sessionIdBytes, UniqueId.LENGTH); @@ -121,16 +201,23 @@ class MailboxIntroductionValidator extends BdfMessageValidator { byte[] previousMessageId = body.getRaw(2); checkLength(previousMessageId, UniqueId.LENGTH); - byte[] mac = body.getRaw(3); + BdfDictionary transportProperties = body.getDictionary(3); + if (transportProperties.size() < 1) throw new FormatException(); + clientHelper + .parseAndValidateTransportPropertiesMap(transportProperties); + + byte[] mac = body.getRaw(4); checkLength(mac, MAC_BYTES); - byte[] signature = body.getRaw(4); + byte[] signature = body.getRaw(5); checkLength(signature, 1, MAX_SIGNATURE_BYTES); + long messageCounter = body.getLong(6); + SessionId sessionId = new SessionId(sessionIdBytes); BdfDictionary meta = messageEncoder .encodeMetadata(MAILBOX_AUTH, sessionId, m.getTimestamp(), - false, 0); + false, messageCounter); MessageId dependency = new MessageId(previousMessageId); return new BdfMessageContext(meta, Collections.singletonList(dependency)); diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java index ed8414e5c..76ab219ed 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java @@ -3,11 +3,15 @@ package org.briarproject.briar.mailbox; import org.briarproject.bramble.api.data.BdfDictionary; 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.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.briar.api.client.SessionId; +import java.util.Map; + import javax.annotation.Nullable; @NotNullByDefault @@ -31,14 +35,28 @@ interface MailboxMessageEncoder { long timestamp, @Nullable MessageId previousMessageId, SessionId sessionId, byte[] ephemeralPublicKey, byte[] mac, byte[] signature, + long acceptTimestamp, + long messageCounter); + + Message encodeMailboxAcceptMessage(GroupId contactGroupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, MessageType type, byte[] ephemeralPublicKey, + long acceptTimestamp, long messageCounter); Message encodeIntroduceeRequestMessage(GroupId contactGroupId, long timestamp, @Nullable MessageId previousMessageId, - SessionId sessionId, + SessionId sessionId, MessageType type, Author author, byte[] ephemeralPublicKey, long acceptTimestamp, long messageCounter); + Message encodeMailboxAuthMessage(GroupId contactGroupId, + long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId, + Map<TransportId, TransportProperties> transportProperties, + byte[] mac, byte[] signature, + long messageCounter); + Message encodeDeclineMessage(GroupId contactGroupId, long timestamp, @Nullable MessageId previousMessageId, SessionId sessionId); diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java index c1bdc76d8..6fe872150 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java @@ -6,12 +6,16 @@ import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfList; 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.properties.TransportProperties; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.api.client.SessionId; +import java.util.Map; + import javax.annotation.Nullable; import javax.inject.Inject; @@ -23,7 +27,8 @@ import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_SESSI import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_TIMESTAMP; import static org.briarproject.briar.mailbox.MessageType.ABORT; import static org.briarproject.briar.mailbox.MessageType.DECLINE; -import static org.briarproject.briar.mailbox.MessageType.MAILBOX_ACCEPT; +import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_ACCEPT; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_AUTH; import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST; @NotNullByDefault @@ -82,12 +87,26 @@ class MailboxMessageEncoderImpl implements MailboxMessageEncoder { return createMessage(contactGroupId, timestamp, body); } + @Override + public Message encodeMailboxAcceptMessage(GroupId contactGroupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, MessageType type, + byte[] ephemeralPublicKey, + long acceptTimestamp, long messageCounter) { + BdfList body = BdfList.of(type.getValue(), sessionId, + previousMessageId, ephemeralPublicKey, acceptTimestamp, + messageCounter); + return createMessage(contactGroupId, timestamp, body); + } + @Override public Message encodeIntroduceeRequestMessage(GroupId contactGroupId, long timestamp, @Nullable MessageId previousMessageId, - SessionId sessionId, byte[] ephemeralPublicKey, + SessionId sessionId, MessageType type, Author author, + byte[] ephemeralPublicKey, long acceptTimestamp, long messageCounter) { - BdfList body = BdfList.of(MAILBOX_ACCEPT.getValue(), sessionId, + BdfList body = BdfList.of(type.getValue(), sessionId, + clientHelper.toList(author), previousMessageId, ephemeralPublicKey, acceptTimestamp, messageCounter); return createMessage(contactGroupId, timestamp, body); @@ -97,10 +116,23 @@ class MailboxMessageEncoderImpl implements MailboxMessageEncoder { public Message encodeIntroduceeAcceptMessage(GroupId contactGroupId, long timestamp, @Nullable MessageId previousMessageId, SessionId sessionId, byte[] ephemeralPublicKey, byte[] mac, - byte[] signature, long messageCounter) { - BdfList body = BdfList.of(MAILBOX_ACCEPT.getValue(), sessionId, + byte[] signature, long acceptTimestamp, long messageCounter) { + BdfList body = BdfList.of(INTRODUCEE_ACCEPT.getValue(), sessionId, previousMessageId, ephemeralPublicKey, mac, signature, - timestamp, + acceptTimestamp, + messageCounter); + return createMessage(contactGroupId, timestamp, body); + } + + @Override + public Message encodeMailboxAuthMessage(GroupId contactGroupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, + Map<TransportId, TransportProperties> transportProperties, + byte[] mac, + byte[] signature, long messageCounter) { + BdfList body = BdfList.of(MAILBOX_AUTH.getValue(), sessionId, + previousMessageId, clientHelper.toDictionary(transportProperties), mac, signature, messageCounter); return createMessage(contactGroupId, timestamp, body); } diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java index 6ee90e89a..4c928a78d 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java @@ -19,9 +19,15 @@ interface MailboxMessageParser { RequestMessage parseRequestMessage(Message m, BdfList body) throws FormatException; - IntroduceeRequestMessage parseMailboxAcceptMessage(Message m, BdfList body) + MailboxAcceptMessage parseMailboxAcceptMessage(Message m, BdfList body) throws FormatException; + IntroduceeRequestMessage parseIntroduceeRequestMessage(Message m, + BdfList body) throws FormatException; + + IntroduceeAcceptMessage parseIntroduceeAcceptMessage(Message m, + BdfList body) throws FormatException; + DeclineMessage parseDeclineMessage(Message m, BdfList body) throws FormatException; diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java index 944bf0e08..a040aa3d7 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java @@ -7,10 +7,14 @@ import org.briarproject.bramble.api.data.BdfEntry; import org.briarproject.bramble.api.data.BdfList; 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.properties.TransportProperties; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.api.client.SessionId; +import java.util.Map; + import javax.inject.Inject; import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; @@ -67,7 +71,7 @@ class MailboxMessageParserImpl implements MailboxMessageParser { } @Override - public IntroduceeRequestMessage parseMailboxAcceptMessage(Message m, + public MailboxAcceptMessage parseMailboxAcceptMessage(Message m, BdfList body) throws FormatException { SessionId sessionId = new SessionId(body.getRaw(1)); byte[] previousMsgBytes = body.getOptionalRaw(2); @@ -75,11 +79,42 @@ class MailboxMessageParserImpl implements MailboxMessageParser { new MessageId(previousMsgBytes)); byte[] ephemeralPublicKey = body.getRaw(3); long acceptTimestamp = body.getLong(4); - return new IntroduceeRequestMessage(m.getId(), m.getGroupId(), + return new MailboxAcceptMessage(m.getId(), m.getGroupId(), m.getTimestamp(), previousMessageId, sessionId, ephemeralPublicKey, acceptTimestamp); } + @Override + public IntroduceeRequestMessage parseIntroduceeRequestMessage(Message m, + BdfList body) throws FormatException { + SessionId sessionId = new SessionId(body.getRaw(1)); + Author author = clientHelper.parseAndValidateAuthor(body.getList(2)); + byte[] previousMsgBytes = body.getOptionalRaw(3); + MessageId previousMessageId = (previousMsgBytes == null ? null : + new MessageId(previousMsgBytes)); + byte[] ephemeralPublicKey = body.getRaw(4); + long acceptTimestamp = body.getLong(5); + return new IntroduceeRequestMessage(m.getId(), m.getGroupId(), + m.getTimestamp(), previousMessageId, sessionId, author, + ephemeralPublicKey, acceptTimestamp); + } + + @Override + public IntroduceeAcceptMessage parseIntroduceeAcceptMessage(Message m, + BdfList body) throws FormatException { + SessionId sessionId = new SessionId(body.getRaw(1)); + byte[] previousMsgBytes = body.getOptionalRaw(2); + MessageId previousMessageId = (previousMsgBytes == null ? null : + new MessageId(previousMsgBytes)); + byte[] ephemeralPublicKey = body.getRaw(3); + byte[] mac = body.getRaw(4); + byte[] signature = body.getRaw(5); + long acceptTimestamp = body.getLong(6); + return new IntroduceeAcceptMessage(m.getId(), m.getGroupId(), + m.getTimestamp(), previousMessageId, sessionId, + ephemeralPublicKey, mac, signature, acceptTimestamp); + } + @Override public DeclineMessage parseDeclineMessage(Message m, BdfList body) throws FormatException { @@ -94,16 +129,17 @@ class MailboxMessageParserImpl implements MailboxMessageParser { @Override public MailboxAuthMessage parseMailboxAuthMessage(Message m, BdfList body) throws FormatException { - /* SessionId sessionId = new SessionId(body.getRaw(1)); byte[] previousMsgBytes = body.getRaw(2); MessageId previousMessageId = new MessageId(previousMsgBytes); - byte[] mac = body.getRaw(3); - byte[] signature = body.getRaw(4); - return new MailboxAuthMessage(m.getId(), m.getGroupId(), m.getTimestamp(), - previousMessageId, sessionId, mac); - */ - return null; + Map<TransportId, TransportProperties> transportProperties = clientHelper + .parseAndValidateTransportPropertiesMap(body.getDictionary(3)); + byte[] mac = body.getRaw(4); + byte[] signature = body.getRaw(5); + return new MailboxAuthMessage(m.getId(), m.getGroupId(), + m.getTimestamp(), + previousMessageId, sessionId, transportProperties, mac, + signature); } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java index e134fc57c..82f96d31b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java @@ -3,28 +3,46 @@ package org.briarproject.briar.mailbox; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.bramble.api.properties.TransportPropertyManager; 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.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.mailbox.event.MailboxIntroductionRequestReceivedEvent; +import org.briarproject.briar.api.mailbox.event.MailboxIntroductionSucceededEvent; + +import java.security.GeneralSecurityException; +import java.util.Map; +import java.util.logging.Logger; import javax.annotation.Nullable; import javax.inject.Inject; -import static org.briarproject.briar.mailbox.IntroduceeState.AWAIT_REMOTE_RESPONSE; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.mailbox.MailboxState.AWAIT_REMOTE_RESPONSE; +import static org.briarproject.briar.mailbox.MailboxState.CONTACT_ADDED; class MailboxProtocolEngine extends AbstractProtocolEngine<MailboxSession> { + private final static Logger LOG = + Logger.getLogger(MailboxProtocolEngine.class.getName()); + @Inject MailboxProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, ContactManager contactManager, @@ -32,10 +50,12 @@ class MailboxProtocolEngine extends AbstractProtocolEngine<MailboxSession> { MessageTracker messageTracker, IdentityManager identityManager, MailboxMessageParser messageParser, MailboxMessageEncoder messageEncoder, Clock clock, - MailboxIntroductionCrypto crypto) { + MailboxIntroductionCrypto crypto, + KeyManager keyManager, + TransportPropertyManager transportPropertyManager) { super(db, clientHelper, contactManager, contactGroupFactory, messageTracker, identityManager, messageParser, messageEncoder, - clock, crypto); + clock, crypto, keyManager, transportPropertyManager); } @Override @@ -67,7 +87,7 @@ class MailboxProtocolEngine extends AbstractProtocolEngine<MailboxSession> { case LOCAL_DECLINED: case LOCAL_ACCEPTED: case AWAIT_REMOTE_RESPONSE: - case MAILBOX_ADDED: + case CONTACT_ADDED: return abort(txn, session); default: throw new AssertionError(); @@ -101,7 +121,8 @@ class MailboxProtocolEngine extends AbstractProtocolEngine<MailboxSession> { //TODO: Check for reasons to decline and if any, move to LOCAL_DECLINE // Move to the AWAIT_REMOTE_RESPONSE state return MailboxSession - .addLocalAccept(s, AWAIT_REMOTE_RESPONSE, reply, publicKey, + .addLocalAccept(s, AWAIT_REMOTE_RESPONSE, reply, + publicKey, privateKey, localTimestamp); } @@ -126,7 +147,7 @@ class MailboxProtocolEngine extends AbstractProtocolEngine<MailboxSession> { @Override public MailboxSession onMailboxAcceptMessage(Transaction txn, - MailboxSession session, IntroduceeRequestMessage m) + MailboxSession session, MailboxAcceptMessage m) throws DbException, FormatException { return null; } @@ -165,6 +186,75 @@ class MailboxProtocolEngine extends AbstractProtocolEngine<MailboxSession> { return null; } + @Override + public MailboxSession onIntroduceeAcceptMessage(Transaction txn, + MailboxSession s, IntroduceeAcceptMessage m) throws DbException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s.getLastLocalMessageId(), + m.getPreviousMessageId())) + return abort(txn, s); + // Broadcast IntroductionRequestReceivedEvent + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + try { + SecretKey secretKey = + crypto.deriveMasterKey(s.local.ephemeralPublicKey, + s.local.ephemeralPrivateKey, + m.getEphemeralPublicKey(), true); + SecretKey aliceMacKey = crypto.deriveMacKey(secretKey, true); + SecretKey bobMacKey = crypto.deriveMacKey(secretKey, false); + + s = MailboxSession.addIntroduceeAccept(s, m.getMessageId(), + m.getEphemeralPublicKey(), secretKey, aliceMacKey, + bobMacKey, + m.getAcceptTimestamp()); + crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId()); + crypto.verifySignature(m.getSignature(), s); + long timestamp = Math.min(s.getLocal().acceptTimestamp, + s.getRemote().acceptTimestamp); + if (timestamp == -1) throw new AssertionError(); + byte[] mac = crypto.authMac(aliceMacKey, s, localAuthor.getId()); + byte[] signature = + crypto.sign(aliceMacKey, localAuthor.getPrivateKey()); + + contactManager + .addContact(txn, s.getRemote().author, localAuthor.getId(), + false, true); + // Only add transport properties and keys when the contact was added + // This will be changed once we have a way to reset state for peers + // that were contacts already at some point in the past. + Contact c = contactManager + .getContact(txn, s.getRemote().author.getId(), + localAuthor.getId()); + // add the keys to the new contact + //noinspection ConstantConditions + Map<TransportId, KeySetId> keys = keyManager + .addContact(txn, c.getId(), secretKey, + timestamp, s.getLocal().alice, true); + // add signed transport properties for the contact + //noinspection ConstantConditions + transportPropertyManager.addRemoteProperties(txn, c.getId(), + transportPropertyManager.getLocalAnonymizedProperties(txn)); + // Broadcast MailboxIntroductionSucceededEvent, because contact got added + MailboxIntroductionSucceededEvent e = + new MailboxIntroductionSucceededEvent(c); + txn.attach(e); + Map<TransportId, TransportProperties> transportProperties = + transportPropertyManager.getLocalProperties(txn); + Message reply = + sendMailboxAuthMessage(txn, s, clock.currentTimeMillis(), + transportProperties, mac, signature, + s.getMessageCounter()); + //TODO: Check for reasons to decline and if any, move to LOCAL_DECLINE + return MailboxSession + .clear(s, CONTACT_ADDED, reply.getId(), + s.getLocalTimestamp(), m.getMessageId()); + } catch (GeneralSecurityException e) { + logException(LOG, WARNING, e); + return abort(txn, s); + } + + } + private boolean isInvalidDependency(AbstractIntroduceeSession s, @Nullable MessageId dependency) { diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java index c9974ba92..a9a75c8b6 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java @@ -17,15 +17,14 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import static org.briarproject.briar.api.mailbox.Role.MAILBOX; -import static org.briarproject.briar.mailbox.IntroduceeState.AWAIT_REMOTE_RESPONSE; -import static org.briarproject.briar.mailbox.IntroduceeState.START; +import static org.briarproject.briar.mailbox.MailboxState.START; @Immutable @NotNullByDefault -class MailboxSession extends AbstractIntroduceeSession { +class MailboxSession extends AbstractIntroduceeSession<MailboxState> { - MailboxSession(SessionId sessionId, IntroduceeState state, + MailboxSession(SessionId sessionId, MailboxState state, long requestTimestamp, GroupId contactGroupId, Author introducer, Local local, Remote remote, @Nullable byte[] masterKey, @Nullable Map<TransportId, KeySetId> transportKeys, @@ -41,50 +40,22 @@ class MailboxSession extends AbstractIntroduceeSession { Remote remote = new Remote(!localIsAlice, remoteAuthor, null, null, null, -1, null); - return new MailboxSession(sessionId, START, -1, contactGroupId, + return new MailboxSession(sessionId, START, -1, + contactGroupId, introducer, local, remote, null, null, 0); } static MailboxSession addLocalAccept(MailboxSession s, - IntroduceeState state, Message m, byte[] ephemeralPublicKey, + MailboxState state, Message m, byte[] ephemeralPublicKey, byte[] ephemeralPrivateKey, long acceptTimestamp) { Local local = new Local(s.local.alice, m.getId(), m.getTimestamp(), ephemeralPublicKey, ephemeralPrivateKey, acceptTimestamp, null); return new MailboxSession(s.getSessionId(), state, m.getTimestamp(), s.contactGroupId, s.introducer, local, s.remote, s.masterKey, - s.transportKeys, s.getMessageCounter() + 1); + s.transportKeys, s.getMessageCounter()); } - static MailboxSession addLocalAuth(MailboxSession s, IntroduceeState state, - Message m, SecretKey masterKey, SecretKey aliceMacKey, - SecretKey bobMacKey) { - // add mac key and sent message - Local local = new Local(s.local.alice, m.getId(), m.getTimestamp(), - s.local.ephemeralPublicKey, s.local.ephemeralPrivateKey, - s.local.acceptTimestamp, - s.local.alice ? aliceMacKey.getBytes() : bobMacKey.getBytes()); - // just add the mac key - Remote remote = new Remote(s.remote.alice, s.remote.author, - s.remote.lastMessageId, s.remote.ephemeralPublicKey, - s.remote.transportProperties, s.remote.acceptTimestamp, - s.remote.alice ? aliceMacKey.getBytes() : bobMacKey.getBytes()); - // add master key - return new MailboxSession(s.getSessionId(), state, - s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, - remote, masterKey.getBytes(), s.transportKeys, - s.getMessageCounter()); - } - - static MailboxSession awaitAuth(MailboxSession s, MailboxAuthMessage m, - Message sent, @Nullable Map<TransportId, KeySetId> transportKeys) { - Local local = new Local(s.local, sent.getId(), sent.getTimestamp()); - Remote remote = new Remote(s.remote, m.getMessageId()); - return new MailboxSession(s.getSessionId(), AWAIT_REMOTE_RESPONSE, - s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, - remote, null, transportKeys, s.getMessageCounter()); - } - - static MailboxSession clear(MailboxSession s, IntroduceeState state, + static MailboxSession clear(MailboxSession s, MailboxState state, @Nullable MessageId lastLocalMessageId, long localTimestamp, @Nullable MessageId lastRemoteMessageId) { Local local = @@ -98,6 +69,24 @@ class MailboxSession extends AbstractIntroduceeSession { remote, null, null, s.getMessageCounter()); } + static MailboxSession addIntroduceeAccept(MailboxSession s, + MessageId lastMessageId, + byte[] ephemeralPublicKey, SecretKey masterKey, + SecretKey aliceMacKey, + SecretKey bobMacKey, long acceptTimestamp) { + Local local = new Local(s.local.alice, s.local.lastMessageId, + s.local.lastMessageTimestamp, s.local.ephemeralPublicKey, + s.local.ephemeralPrivateKey, s.local.acceptTimestamp, + aliceMacKey.getBytes()); + Remote remote = + new Remote(false, s.remote.author, lastMessageId, + ephemeralPublicKey, null, acceptTimestamp, + bobMacKey.getBytes()); + return new MailboxSession(s.getSessionId(), s.getState(), + s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, + remote, masterKey.getBytes(), null, s.getMessageCounter()); + } + @Override Role getRole() { return MAILBOX; diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java index 0d42aefd7..b3b439dd8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java @@ -22,4 +22,7 @@ interface MailboxSessionParser { MailboxSession parseMailboxSession(GroupId introducerGroupId, BdfDictionary d) throws FormatException; + IntroduceeSession parseIntroduceeSession(GroupId introducerGroupId, + BdfDictionary d) throws FormatException; + } diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java index b7beb49ff..e74ee5238 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java @@ -22,6 +22,7 @@ import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.briar.api.mailbox.Role.INTRODUCEE; +import static org.briarproject.briar.api.mailbox.Role.MAILBOX; import static org.briarproject.briar.api.mailbox.Role.OWNER; import static org.briarproject.briar.api.mailbox.Role.fromValue; import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Local; @@ -109,6 +110,25 @@ class MailboxSessionParserImpl implements MailboxSessionParser { @Override public MailboxSession parseMailboxSession(GroupId introducerGroupId, BdfDictionary d) throws FormatException { + if (getRole(d) != MAILBOX) throw new IllegalArgumentException(); + SessionId sessionId = getSessionId(d); + long sessionCounter = getSessionCounter(d); + MailboxState state = MailboxState.fromValue(getState(d)); + long requestTimestamp = d.getLong(SESSION_KEY_REQUEST_TIMESTAMP); + Author introducer = getAuthor(d, SESSION_KEY_INTRODUCER); + 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<TransportId, KeySetId> transportKeys = parseTransportKeys( + d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS)); + return new MailboxSession(sessionId, state, requestTimestamp, + introducerGroupId, introducer, local, remote, masterKey, + transportKeys, sessionCounter); + } + + @Override + public IntroduceeSession parseIntroduceeSession(GroupId introducerGroupId, + BdfDictionary d) throws FormatException { if (getRole(d) != INTRODUCEE) throw new IllegalArgumentException(); SessionId sessionId = getSessionId(d); long sessionCounter = getSessionCounter(d); @@ -120,7 +140,7 @@ class MailboxSessionParserImpl implements MailboxSessionParser { byte[] masterKey = d.getOptionalRaw(SESSION_KEY_MASTER_KEY); Map<TransportId, KeySetId> transportKeys = parseTransportKeys( d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS)); - return new MailboxSession(sessionId, state, requestTimestamp, + return new IntroduceeSession(sessionId, state, requestTimestamp, introducerGroupId, introducer, local, remote, masterKey, transportKeys, sessionCounter); } diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxState.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxState.java index 6a44a0c4d..ecffd8110 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxState.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxState.java @@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable; enum MailboxState implements State { START(0), - AWAIT_LOCAL_RESPONSES(1), + AWAIT_LOCAL_RESPONSE(1), LOCAL_DECLINED(2), LOCAL_ACCEPTED(3), AWAIT_REMOTE_RESPONSE(4), diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java index 0cfd4c22d..d54397a74 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java @@ -3,20 +3,26 @@ package org.briarproject.briar.mailbox; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.mailbox.event.MailboxIntroductionResponseReceivedEvent; +import org.briarproject.briar.api.mailbox.event.MailboxIntroductionSucceededEvent; import javax.inject.Inject; +import static org.briarproject.briar.mailbox.OwnerState.ADDED; +import static org.briarproject.briar.mailbox.OwnerState.AWAIT_AUTH_M; import static org.briarproject.briar.mailbox.OwnerState.AWAIT_RESPONSE_B; import static org.briarproject.briar.mailbox.OwnerState.AWAIT_RESPONSE_M; @@ -29,10 +35,11 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> { MessageTracker messageTracker, IdentityManager identityManager, MailboxMessageParser messageParser, MailboxMessageEncoder messageEncoder, Clock clock, - MailboxIntroductionCrypto crypto) { + MailboxIntroductionCrypto crypto, KeyManager keyManager, + TransportPropertyManager transportPropertyManager) { super(db, clientHelper, contactManager, contactGroupFactory, messageTracker, identityManager, messageParser, messageEncoder, - clock, crypto); + clock, crypto, keyManager, transportPropertyManager); } @Override @@ -74,7 +81,7 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> { @Override public OwnerSession onMailboxAcceptMessage(Transaction txn, OwnerSession s, - IntroduceeRequestMessage m) throws DbException, FormatException { + MailboxAcceptMessage m) throws DbException, FormatException { switch (s.getState()) { case START: case AWAIT_RESPONSE_M: @@ -105,20 +112,22 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> { } private OwnerSession onMailboxAccept(Transaction txn, OwnerSession s, - IntroduceeRequestMessage m) throws DbException { + MailboxAcceptMessage m) throws DbException { // The dependency, if any, must be the last remote message if (isInvalidDependency(s.getMailbox().lastRemoteMessageId, m.getPreviousMessageId())) return abort(txn, s); Message forward = sendIntroduceeRequestMessage(txn, s.getIntroducee(), - clock.currentTimeMillis(), m.getEphemeralPublicKey(), - m.getAcceptTimestamp(), s.getMessageCounter()); + clock.currentTimeMillis(), s.getMailbox().author, + m.getEphemeralPublicKey(), + m.getAcceptTimestamp(), + s.getMessageCounter()); broadcastMailboxIntroductionResponseReceived(txn, s.getMailbox().author, s.getIntroducee().author); return new OwnerSession(s.getSessionId(), AWAIT_RESPONSE_B, s.getRequestTimestamp(), new Introducee(s.getMailbox(), m.getMessageId()), new Introducee(s.getIntroducee(), forward), - s.getMessageCounter() + 1); + s.getMessageCounter()); } @Override @@ -128,9 +137,24 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> { } @Override - public OwnerSession onAuthMessage(Transaction txn, OwnerSession session, + public OwnerSession onAuthMessage(Transaction txn, OwnerSession s, MailboxAuthMessage m) throws DbException, FormatException { - return null; + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s.getMailbox().getLastLocalMessageId(), + m.getPreviousMessageId())) return abort(txn, s); + Message forward = sendMailboxAuthMessage(txn, s.getIntroducee(), + clock.currentTimeMillis(), m.getTransportProperties(), + m.getMac(), m.getSignature(), s.getMessageCounter()); + Contact c = contactManager + .getContact(txn, s.getIntroducee().author.getId(), + identityManager.getLocalAuthor().getId()); + db.setMailboxForContact(txn, c.getId(), null, c.getId()); + txn.attach(new MailboxIntroductionSucceededEvent(c)); + return new OwnerSession(s.getSessionId(), ADDED, + s.getRequestTimestamp(), + new Introducee(s.getMailbox(), m.getMessageId()), + new Introducee(s.getIntroducee(), forward), + s.getMessageCounter()); } @Override @@ -139,6 +163,26 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> { return null; } + @Override + public OwnerSession onIntroduceeAcceptMessage(Transaction txn, + OwnerSession s, IntroduceeAcceptMessage m) throws DbException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s.getIntroducee().getLastLocalMessageId(), + m.getPreviousMessageId())) return abort(txn, s); + Message forward = sendIntroduceeResponseMessage(txn, s.getMailbox(), + s.getMailbox().lastRemoteMessageId, + clock.currentTimeMillis(), + m.getEphemeralPublicKey(), + m.getMac(), m.getSignature(), + m.getAcceptTimestamp(), + s.getMessageCounter()); + return new OwnerSession(s.getSessionId(), AWAIT_AUTH_M, + s.getRequestTimestamp(), + new Introducee(s.getMailbox(), forward), + new Introducee(s.getIntroducee(), m.getMessageId()), + s.getMessageCounter()); + } + private OwnerSession onLocalRequest(Transaction txn, OwnerSession s, long timestamp) throws DbException { // Send REQUEST messages @@ -172,5 +216,4 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> { new MailboxIntroductionResponseReceivedEvent(from, to); txn.attach(e); } - } diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java index ec7c9c448..d07b5ac22 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java @@ -8,7 +8,6 @@ import org.briarproject.briar.api.mailbox.Role; import javax.annotation.concurrent.Immutable; -import static org.briarproject.briar.api.mailbox.Role.MAILBOX; import static org.briarproject.briar.api.mailbox.Role.OWNER; @Immutable @@ -32,6 +31,10 @@ class OwnerSession extends Session<OwnerState> { new Introducee(sessionId, groupIdB, authorB), sessionCounter); } + public static OwnerSession finished(OwnerSession s) { + return null; + } + @Override Role getRole() { return OWNER; diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java index edcd5b63e..2ab36fcc1 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java @@ -21,7 +21,7 @@ interface ProtocolEngine<S extends Session> { throws DbException, FormatException; S onMailboxAcceptMessage(Transaction txn, S session, - IntroduceeRequestMessage m) + MailboxAcceptMessage m) throws DbException, FormatException; S onIntroduceeRequestMessage(Transaction txn, @@ -41,4 +41,6 @@ interface ProtocolEngine<S extends Session> { S onAbortMessage(Transaction txn, S session, AbortMessage m) throws DbException, FormatException; + S onIntroduceeAcceptMessage(Transaction txn, S session, + IntroduceeAcceptMessage acceptMessage) throws DbException; } diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/Session.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/Session.java index 5e7a75bac..ab65947ed 100644 --- a/briar-core/src/main/java/org/briarproject/briar/mailbox/Session.java +++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/Session.java @@ -41,8 +41,4 @@ abstract class Session<S extends State> { return messageCounter; } - synchronized void increaseMessageCounter() { - messageCounter++; - } - } diff --git a/briar-core/src/test/java/org/briarproject/briar/forum/ForumManagerTest.java b/briar-core/src/test/java/org/briarproject/briar/forum/ForumManagerTest.java index d53af1730..52625831f 100644 --- a/briar-core/src/test/java/org/briarproject/briar/forum/ForumManagerTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/forum/ForumManagerTest.java @@ -7,6 +7,7 @@ import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumPost; import org.briarproject.briar.api.forum.ForumPostHeader; import org.briarproject.briar.api.forum.ForumSharingManager; +import org.briarproject.briar.mailbox.DaggerMailboxIntroductionIntegrationTestComponent; import org.briarproject.briar.test.BriarIntegrationTest; import org.briarproject.briar.test.BriarIntegrationTestComponent; import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent; @@ -69,6 +70,9 @@ public class ForumManagerTest c2 = DaggerBriarIntegrationTestComponent.builder() .testDatabaseModule(new TestDatabaseModule(t2Dir)).build(); injectEagerSingletons(c2); + cMailbox = DaggerMailboxIntroductionIntegrationTestComponent.builder() + .testDatabaseModule(new TestDatabaseModule(t3Dir)).build(); + injectEagerSingletons(cMailbox); } private ForumPost createForumPost(GroupId groupId, diff --git a/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java index 403904d21..10ca4da97 100644 --- a/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java @@ -25,11 +25,10 @@ import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.introduction.IntroductionMessage; import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; -import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent; -import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent; import org.briarproject.briar.api.mailbox.MailboxIntroductionManager; import org.briarproject.briar.api.mailbox.event.MailboxIntroductionRequestReceivedEvent; import org.briarproject.briar.api.mailbox.event.MailboxIntroductionResponseReceivedEvent; +import org.briarproject.briar.api.mailbox.event.MailboxIntroductionSucceededEvent; import org.briarproject.briar.test.BriarIntegrationTest; import org.briarproject.briar.test.BriarIntegrationTestComponent; import org.junit.Before; @@ -145,6 +144,20 @@ public class MailboxIntroductionIntegrationTest extends // sync/forward RESPONSE message to the contact sync0To1(1, true); eventWaiter.await(TIMEOUT, 1); + sync1To0(1, true); + sync0ToMailbox(1, true); + eventWaiter.await(TIMEOUT, 1); + assertTrue(listenerMailbox.succeeded); + assertEquals(contact1From0.getAuthor(), + listenerMailbox.introduceeContact.getAuthor()); + syncMailboxTo0(1, true); + eventWaiter.await(TIMEOUT, 1); + assertTrue(listener0.success); + sync0To1(1, true); + eventWaiter.await(TIMEOUT, 1); + assertTrue(listener1.succeeded); + assertEquals(privateMailboxFrom0.getAuthor(), + listener1.introduceeContact.getAuthor()); } @@ -220,6 +233,7 @@ public class MailboxIntroductionIntegrationTest extends private volatile boolean succeeded = false; private volatile boolean answerRequests = true; private volatile SessionId sessionId; + private volatile Contact introduceeContact; private final int introducee; private final boolean accept; @@ -256,12 +270,14 @@ public class MailboxIntroductionIntegrationTest extends // only broadcast for DECLINE messages in introducee role latestEvent = e; eventWaiter.resume(); - } else if (e instanceof IntroductionSucceededEvent) { + } else if (e instanceof MailboxIntroductionSucceededEvent) { latestEvent = e; succeeded = true; - Contact contact = ((IntroductionSucceededEvent) e).getContact(); + Contact contact = + ((MailboxIntroductionSucceededEvent) e).getContact(); eventWaiter .assertFalse(contact.getId().equals(contactId0From1)); + introduceeContact = contact; eventWaiter.resume(); } else if (e instanceof IntroductionAbortedEvent) { latestEvent = e; @@ -283,6 +299,7 @@ public class MailboxIntroductionIntegrationTest extends private volatile boolean response1Received = false; private volatile boolean response2Received = false; + private volatile boolean success = false; @Override public void eventOccurred(Event e) { @@ -294,6 +311,10 @@ public class MailboxIntroductionIntegrationTest extends response1Received = true; } eventWaiter.resume(); + } else if (e instanceof MailboxIntroductionSucceededEvent) { + latestEvent = e; + success = true; + eventWaiter.resume(); } } diff --git a/mailbox-android/build.gradle b/mailbox-android/build.gradle index 9484df25e..043812701 100644 --- a/mailbox-android/build.gradle +++ b/mailbox-android/build.gradle @@ -4,6 +4,7 @@ apply from: 'witness.gradle' dependencies { implementation project(path: ':bramble-core', configuration: 'default') + implementation project(path: ':mailbox-core', configuration: 'default') implementation project(':bramble-android') def lifecycle_version = "1.1.1" diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/AndroidComponent.java b/mailbox-android/src/main/java/org/briarproject/mailbox/android/AndroidComponent.java index 4d20692cf..ab916491e 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/AndroidComponent.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/AndroidComponent.java @@ -23,6 +23,8 @@ import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.plugin.tor.CircumventionProvider; +import org.briarproject.briar.MailboxCoreEagerSingletons; +import org.briarproject.briar.MailboxCoreModule; import org.briarproject.mailbox.android.login.SignInReminderReceiver; import org.briarproject.mailbox.android.reporting.BriarReportSender; import org.briarproject.mailbox.api.android.AndroidNotificationManager; @@ -42,11 +44,12 @@ import dagger.Component; @Component(modules = { BrambleCoreModule.class, BrambleAndroidModule.class, + MailboxCoreModule.class, BriarAccountModule.class, AppModule.class }) public interface AndroidComponent - extends BrambleCoreEagerSingletons { + extends BrambleCoreEagerSingletons, MailboxCoreEagerSingletons { // Exposed objects @CryptoExecutor diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxApplicationImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxApplicationImpl.java index fe5842c1a..da14c0ecc 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxApplicationImpl.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxApplicationImpl.java @@ -13,6 +13,7 @@ import org.acra.ACRA; import org.acra.ReportingInteractionMode; import org.acra.annotation.ReportsCrashes; import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.briar.MailboxCoreModule; import org.briarproject.mailbox.R; import org.briarproject.mailbox.android.logging.CachingLogHandler; import org.briarproject.mailbox.android.reporting.BriarReportPrimer; @@ -119,6 +120,7 @@ public class MailboxApplicationImpl extends Application // We need to load the eager singletons directly after making the // dependency graphs BrambleCoreModule.initEagerSingletons(androidComponent); + MailboxCoreModule.initEagerSingletons(androidComponent); AndroidEagerSingletons.initEagerSingletons(androidComponent); return androidComponent; } diff --git a/mailbox-core/build.gradle b/mailbox-core/build.gradle new file mode 100644 index 000000000..6fb4231dc --- /dev/null +++ b/mailbox-core/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'java-library' +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +apply plugin: 'ru.vyarus.animalsniffer' +apply plugin: 'net.ltgt.apt' +apply plugin: 'idea' +apply plugin: 'witness' +apply from: 'witness.gradle' + +dependencies { + implementation project(path: ':briar-api', configuration: 'default') + implementation 'com.rometools:rome:1.7.3' + implementation 'org.jdom:jdom2:2.0.6' + implementation 'com.squareup.okhttp3:okhttp:3.8.0' + implementation 'org.jsoup:jsoup:1.10.3' + + apt 'com.google.dagger:dagger-compiler:2.0.2' + + testImplementation project(path: ':bramble-core', configuration: 'default') + testImplementation project(path: ':bramble-core', configuration: 'testOutput') + testImplementation project(path: ':bramble-api', configuration: 'testOutput') + testImplementation 'net.jodah:concurrentunit:0.4.2' + testImplementation 'junit:junit:4.12' + testImplementation "org.jmock:jmock:2.8.2" + testImplementation "org.jmock:jmock-junit4:2.8.2" + testImplementation "org.jmock:jmock-legacy:2.8.2" + testImplementation "org.hamcrest:hamcrest-library:1.3" + testImplementation "org.hamcrest:hamcrest-core:1.3" + + testApt 'com.google.dagger:dagger-compiler:2.0.2' + + signature 'org.codehaus.mojo.signature:java16:1.1@signature' +} + +tasks.withType(Test) { + // Use entropy-gathering device specified on command line, if any + systemProperty 'java.security.egd', System.getProperty('java.security.egd') +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/MailboxCoreEagerSingletons.java b/mailbox-core/src/main/java/org/briarproject/briar/MailboxCoreEagerSingletons.java new file mode 100644 index 000000000..7693b2121 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/MailboxCoreEagerSingletons.java @@ -0,0 +1,9 @@ +package org.briarproject.briar; + +import org.briarproject.briar.mailbox.MailboxIntroductionModule; + +public interface MailboxCoreEagerSingletons { + + void inject(MailboxIntroductionModule.EagerSingletons init); + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/MailboxCoreModule.java b/mailbox-core/src/main/java/org/briarproject/briar/MailboxCoreModule.java new file mode 100644 index 000000000..a82fafbbd --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/MailboxCoreModule.java @@ -0,0 +1,15 @@ +package org.briarproject.briar; + +import org.briarproject.briar.mailbox.MailboxIntroductionModule; + +import dagger.Module; + +@Module(includes = { + MailboxIntroductionModule.class, +}) +public class MailboxCoreModule { + + public static void initEagerSingletons(MailboxCoreEagerSingletons c) { + c.inject(new MailboxIntroductionModule.EagerSingletons()); + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbortMessage.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbortMessage.java new file mode 100644 index 000000000..eb326edb4 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbortMessage.java @@ -0,0 +1,27 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class AbortMessage extends AbstractMailboxIntroductionMessage { + + private final SessionId sessionId; + + protected AbortMessage(MessageId messageId, GroupId groupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + } + + public SessionId getSessionId() { + return sessionId; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java new file mode 100644 index 000000000..6bf182a55 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java @@ -0,0 +1,157 @@ +package org.briarproject.briar.mailbox; + +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.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.briar.api.client.SessionId; + +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +abstract class AbstractIntroduceeSession<S extends State> extends Session<S> + implements PeerSession { + + final GroupId contactGroupId; + final Author introducer; + final Local local; + final Remote remote; + @Nullable + final byte[] masterKey; + @Nullable + final Map<TransportId, KeySetId> transportKeys; + + AbstractIntroduceeSession(SessionId sessionId, S state, + long requestTimestamp, GroupId contactGroupId, Author introducer, + Local local, Remote remote, @Nullable byte[] masterKey, + @Nullable Map<TransportId, KeySetId> transportKeys, + long sessionCounter) { + super(sessionId, state, requestTimestamp, sessionCounter); + this.contactGroupId = contactGroupId; + this.introducer = introducer; + this.local = local; + this.remote = remote; + this.masterKey = masterKey; + this.transportKeys = transportKeys; + } + + @Override + public GroupId getContactGroupId() { + return contactGroupId; + } + + @Override + public long getLocalTimestamp() { + return local.lastMessageTimestamp; + } + + @Nullable + @Override + public MessageId getLastLocalMessageId() { + return local.lastMessageId; + } + + @Nullable + @Override + public MessageId getLastRemoteMessageId() { + return remote.lastMessageId; + } + + Author getIntroducer() { + return introducer; + } + + public Local getLocal() { + return local; + } + + public Remote getRemote() { + return remote; + } + + @Nullable + byte[] getMasterKey() { + return masterKey; + } + + @Nullable + Map<TransportId, KeySetId> getTransportKeys() { + return transportKeys; + } + + abstract static class Common { + final boolean alice; + @Nullable + final MessageId lastMessageId; + @Nullable + final byte[] ephemeralPublicKey; + + final long acceptTimestamp; + @Nullable + final byte[] macKey; + + private Common(boolean alice, @Nullable MessageId lastMessageId, + @Nullable byte[] ephemeralPublicKey, @Nullable + long acceptTimestamp, @Nullable byte[] macKey) { + this.alice = alice; + this.lastMessageId = lastMessageId; + this.ephemeralPublicKey = ephemeralPublicKey; + this.acceptTimestamp = acceptTimestamp; + this.macKey = macKey; + } + } + + static class Local extends Common { + final long lastMessageTimestamp; + @Nullable + final byte[] ephemeralPrivateKey; + + Local(boolean alice, @Nullable MessageId lastMessageId, + long lastMessageTimestamp, @Nullable byte[] ephemeralPublicKey, + @Nullable byte[] ephemeralPrivateKey, long acceptTimestamp, + @Nullable byte[] macKey) { + super(alice, lastMessageId, ephemeralPublicKey, acceptTimestamp, + macKey); + this.lastMessageTimestamp = lastMessageTimestamp; + this.ephemeralPrivateKey = ephemeralPrivateKey; + } + + Local(Local s, @Nullable MessageId lastMessageId, + long lastMessageTimestamp) { + this(s.alice, lastMessageId, lastMessageTimestamp, + s.ephemeralPublicKey, s.ephemeralPrivateKey, + s.acceptTimestamp, s.macKey); + } + } + + static class Remote extends Common { + final Author author; + @Nullable + final Map<TransportId, TransportProperties> transportProperties; + + Remote(boolean alice, Author author, + @Nullable MessageId lastMessageId, + @Nullable byte[] ephemeralPublicKey, @Nullable + Map<TransportId, TransportProperties> transportProperties, + long acceptTimestamp, @Nullable byte[] macKey) { + super(alice, lastMessageId, ephemeralPublicKey, acceptTimestamp, + macKey); + this.author = author; + this.transportProperties = transportProperties; + } + + Remote(Remote s, @Nullable MessageId lastMessageId) { + this(s.alice, s.author, lastMessageId, s.ephemeralPublicKey, + s.transportProperties, s.acceptTimestamp, s.macKey); + } + + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractMailboxIntroductionMessage.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractMailboxIntroductionMessage.java new file mode 100644 index 000000000..376480baf --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractMailboxIntroductionMessage.java @@ -0,0 +1,45 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +abstract class AbstractMailboxIntroductionMessage { + + private final MessageId messageId; + private final GroupId groupId; + private final long timestamp; + @Nullable + private final MessageId previousMessageId; + + AbstractMailboxIntroductionMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId) { + this.messageId = messageId; + this.groupId = groupId; + this.timestamp = timestamp; + this.previousMessageId = previousMessageId; + } + + MessageId getMessageId() { + return messageId; + } + + GroupId getGroupId() { + return groupId; + } + + long getTimestamp() { + return timestamp; + } + + @Nullable + MessageId getPreviousMessageId() { + return previousMessageId; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java new file mode 100644 index 000000000..042f062a0 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java @@ -0,0 +1,207 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.bramble.api.properties.TransportPropertyManager; +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.briar.api.client.SessionId; + +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.briar.mailbox.MessageType.ABORT; +import static org.briarproject.briar.mailbox.MessageType.DECLINE; +import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_ACCEPT; +import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_REQUEST; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_ACCEPT; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_AUTH; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST; + +@Immutable +@NotNullByDefault +abstract class AbstractProtocolEngine<S extends Session> + implements ProtocolEngine<S> { + + protected final DatabaseComponent db; + protected final ClientHelper clientHelper; + protected final ContactManager contactManager; + protected final ContactGroupFactory contactGroupFactory; + protected final IdentityManager identityManager; + protected final MailboxMessageParser messageParser; + protected final MailboxMessageEncoder messageEncoder; + protected final Clock clock; + protected final MailboxIntroductionCrypto crypto; + protected final KeyManager keyManager; + protected final TransportPropertyManager transportPropertyManager; + + AbstractProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, + ContactManager contactManager, + ContactGroupFactory contactGroupFactory, + IdentityManager identityManager, + MailboxMessageParser messageParser, + MailboxMessageEncoder messageEncoder, Clock clock, + MailboxIntroductionCrypto crypto, + KeyManager keyManager, + TransportPropertyManager transportPropertyManager) { + this.db = db; + this.clientHelper = clientHelper; + this.contactManager = contactManager; + this.contactGroupFactory = contactGroupFactory; + this.identityManager = identityManager; + this.messageParser = messageParser; + this.messageEncoder = messageEncoder; + this.clock = clock; + this.crypto = crypto; + this.keyManager = keyManager; + this.transportPropertyManager = transportPropertyManager; + } + + Message sendMailboxRequestMessage(Transaction txn, PeerSession s, + long timestamp, Author introduceeAuthor, long messageCounter) + throws DbException { + Message m = messageEncoder + .encodeRequestMessage(s.getContactGroupId(), timestamp, + s.getLastLocalMessageId(), introduceeAuthor, + messageCounter); + sendMessage(txn, MAILBOX_REQUEST, s.getSessionId(), m, messageCounter); + return m; + } + + Message sendMailboxAcceptMessage(Transaction txn, PeerSession s, + long timestamp, + byte[] ephemeralPublicKey, long acceptTimestamp, + long messageCounter) throws DbException { + Message m = messageEncoder + .encodeMailboxAcceptMessage(s.getContactGroupId(), + timestamp, s.getLastLocalMessageId(), s.getSessionId(), + MAILBOX_ACCEPT, + ephemeralPublicKey, + acceptTimestamp, + messageCounter); + sendMessage(txn, MAILBOX_ACCEPT, s.getSessionId(), m, + messageCounter); + return m; + } + + Message sendIntroduceeRequestMessage(Transaction txn, PeerSession s, + long timestamp, Author author, + byte[] ephemeralPublicKey, long acceptTimestamp, + long messageCounter) throws DbException { + Message m = messageEncoder + .encodeIntroduceeRequestMessage(s.getContactGroupId(), + timestamp, s.getLastLocalMessageId(), s.getSessionId(), + INTRODUCEE_REQUEST, + author, + ephemeralPublicKey, + acceptTimestamp, + messageCounter); + sendMessage(txn, INTRODUCEE_REQUEST, s.getSessionId(), m, + messageCounter); + return m; + } + + Message sendIntroduceeResponseMessage(Transaction txn, PeerSession s, + MessageId previousMessage, + long timestamp, byte[] ephemeralPublicKey, byte[] mac, + byte[] signature, long acceptTimestamp, long messageCounter) + throws DbException { + Message m = messageEncoder + .encodeIntroduceeAcceptMessage(s.getContactGroupId(), + timestamp, + previousMessage, s.getSessionId(), + ephemeralPublicKey, mac, signature, acceptTimestamp, + messageCounter); + sendMessage(txn, INTRODUCEE_ACCEPT, s.getSessionId(), m, + messageCounter); + return m; + } + + Message sendMailboxAuthMessage(Transaction txn, PeerSession s, + long timestamp, + Map<TransportId, TransportProperties> transportProperties, + byte[] mac, byte[] signature, long messageCounter) + throws DbException { + Message m = messageEncoder + .encodeMailboxAuthMessage(s.getContactGroupId(), timestamp, + s.getLastRemoteMessageId(), s.getSessionId(), + transportProperties, mac, signature, messageCounter); + sendMessage(txn, MAILBOX_AUTH, s.getSessionId(), m, messageCounter); + return m; + } + + /* + Message sendMailboxInfotMessage(Transaction txn, PeerSession s, + long timestamp, + byte[] ephemeralPublicKey, long acceptTimestamp, + Map<TransportId, TransportProperties> transportProperties, + boolean visible) throws DbException { + Message m = messageEncoder + .encodeMaiAcceptMessage(s.getContactGroupId(), timestamp, + s.getLastLocalMessageId(), s.getSessionId(), + ephemeralPublicKey, acceptTimestamp, + transportProperties); + sendMessage(txn, MAILBOX_AUTH, s.getSessionId(), m); + return m; + } + */ + + Message sendDeclineMessage(Transaction txn, PeerSession s, long timestamp, + boolean visible) throws DbException { + Message m = messageEncoder + .encodeDeclineMessage(s.getContactGroupId(), timestamp, + s.getLastLocalMessageId(), s.getSessionId()); + sendMessage(txn, DECLINE, s.getSessionId(), m, 0); + return m; + } + + Message sendAbortMessage(Transaction txn, PeerSession s, long timestamp) + throws DbException { + Message m = messageEncoder + .encodeAbortMessage(s.getContactGroupId(), timestamp, + s.getLastLocalMessageId(), s.getSessionId()); + sendMessage(txn, ABORT, s.getSessionId(), m, 0); + return m; + } + + private void sendMessage(Transaction txn, MessageType type, + SessionId sessionId, Message m, long messageCounter) + throws DbException { + BdfDictionary meta = messageEncoder + .encodeMetadata(type, sessionId, m.getTimestamp(), true, + messageCounter); + try { + clientHelper.addLocalMessage(txn, m, meta, true); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + boolean isInvalidDependency(@Nullable MessageId lastRemoteMessageId, + @Nullable MessageId dependency) { + if (dependency == null) return lastRemoteMessageId != null; + return lastRemoteMessageId == null || + !dependency.equals(lastRemoteMessageId); + } + + long getLocalTimestamp(long localTimestamp, long requestTimestamp) { + return Math.max(clock.currentTimeMillis(), + Math.max(localTimestamp, requestTimestamp) + 1); + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/BdfIncomingMessageHook.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/BdfIncomingMessageHook.java new file mode 100644 index 000000000..d3dd5b47f --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/BdfIncomingMessageHook.java @@ -0,0 +1,66 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataParser; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Metadata; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.InvalidMessageException; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public abstract class BdfIncomingMessageHook implements IncomingMessageHook { + + protected final DatabaseComponent db; + protected final ClientHelper clientHelper; + protected final MetadataParser metadataParser; + + protected BdfIncomingMessageHook(DatabaseComponent db, + ClientHelper clientHelper, MetadataParser metadataParser) { + this.db = db; + this.clientHelper = clientHelper; + this.metadataParser = metadataParser; + } + + /** + * Called once for each incoming message that passes validation. + * + * @return whether or not this message should be shared + * @throws DbException Should only be used for real database errors. + * If this is thrown, delivery will be attempted again at next startup, + * whereas if a FormatException is thrown, the message will be permanently + * invalidated. + * @throws FormatException Use this for any non-database error + * that occurs while handling remotely created data. + * This includes errors that occur while handling locally created data + * in a context controlled by remotely created data + * (for example, parsing the metadata of a dependency + * of an incoming message). + * Never rethrow DbException as FormatException! + */ + protected abstract boolean incomingMessage(Transaction txn, Message m, + BdfList body, BdfDictionary meta) throws DbException, + FormatException; + + @Override + public boolean incomingMessage(Transaction txn, Message m, Metadata meta) + throws DbException, InvalidMessageException { + try { + BdfList body = clientHelper.toList(m); + BdfDictionary metaDictionary = metadataParser.parse(meta); + return incomingMessage(txn, m, body, metaDictionary); + } catch (FormatException e) { + throw new InvalidMessageException(e); + } + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/ContactAcceptMessage.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/ContactAcceptMessage.java new file mode 100644 index 000000000..8797283b4 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/ContactAcceptMessage.java @@ -0,0 +1,52 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class ContactAcceptMessage extends AbstractMailboxIntroductionMessage { + + private final SessionId sessionId; + private final byte[] ephemeralPublicKey; + private final long acceptTimestamp; + private final byte[] mac; + private final byte[] signature; + + protected ContactAcceptMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, byte[] ephemeralPublicKey, + long acceptTimestamp, byte[] mac, byte[] signature) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + this.ephemeralPublicKey = ephemeralPublicKey; + this.acceptTimestamp = acceptTimestamp; + this.mac = mac; + this.signature = signature; + } + + public SessionId getSessionId() { + return sessionId; + } + + public byte[] getEphemeralPublicKey() { + return ephemeralPublicKey; + } + + public long getAcceptTimestamp() { + return acceptTimestamp; + } + + public byte[] getMac() { + return mac; + } + + public byte[] getSignature() { + return signature; + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/ContactRequestMessage.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/ContactRequestMessage.java new file mode 100644 index 000000000..676ee4500 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/ContactRequestMessage.java @@ -0,0 +1,42 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class ContactRequestMessage extends AbstractMailboxIntroductionMessage { + + private final SessionId sessionId; + private final byte[] ephemeralPublicKey; + private final long acceptTimestamp; + + protected ContactRequestMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, byte[] ephemeralPublicKey, + long acceptTimestamp, + long messageCounter) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + this.ephemeralPublicKey = ephemeralPublicKey; + this.acceptTimestamp = acceptTimestamp; + } + + public SessionId getSessionId() { + return sessionId; + } + + public byte[] getEphemeralPublicKey() { + return ephemeralPublicKey; + } + + public long getAcceptTimestamp() { + return acceptTimestamp; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/DeclineMessage.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/DeclineMessage.java new file mode 100644 index 000000000..b7da465ea --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/DeclineMessage.java @@ -0,0 +1,28 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class DeclineMessage extends AbstractMailboxIntroductionMessage { + + private final SessionId sessionId; + + protected DeclineMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + } + + public SessionId getSessionId() { + return sessionId; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/Introducee.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/Introducee.java new file mode 100644 index 000000000..d4459605f --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/Introducee.java @@ -0,0 +1,77 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +final class Introducee implements PeerSession { + final SessionId sessionId; + final GroupId groupId; + final Author author; + final long localTimestamp; + @Nullable + final MessageId lastLocalMessageId, lastRemoteMessageId; + + Introducee(SessionId sessionId, GroupId groupId, Author author, + long localTimestamp, + @Nullable MessageId lastLocalMessageId, + @Nullable MessageId lastRemoteMessageId) { + this.sessionId = sessionId; + this.groupId = groupId; + this.localTimestamp = localTimestamp; + this.author = author; + this.lastLocalMessageId = lastLocalMessageId; + this.lastRemoteMessageId = lastRemoteMessageId; + } + + Introducee(Introducee i, Message sent) { + this(i.sessionId, i.groupId, i.author, sent.getTimestamp(), + sent.getId(), i.lastRemoteMessageId); + } + + Introducee(Introducee i, MessageId remoteMessageId) { + this(i.sessionId, i.groupId, i.author, i.localTimestamp, + i.lastLocalMessageId, remoteMessageId); + } + + Introducee(SessionId sessionId, GroupId groupId, + Author author) { + this(sessionId, groupId, author, -1, null, null); + } + + public SessionId getSessionId() { + return sessionId; + } + + @Override + public GroupId getContactGroupId() { + return groupId; + } + + @Override + public long getLocalTimestamp() { + return localTimestamp; + } + + @Nullable + @Override + public MessageId getLastLocalMessageId() { + return lastLocalMessageId; + } + + @Nullable + @Override + public MessageId getLastRemoteMessageId() { + return lastRemoteMessageId; + } + +} + diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeAcceptMessage.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeAcceptMessage.java new file mode 100644 index 000000000..ebb481692 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeAcceptMessage.java @@ -0,0 +1,52 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class IntroduceeAcceptMessage extends AbstractMailboxIntroductionMessage { + + private final SessionId sessionId; + private final byte[] ephemeralPublicKey; + private final long acceptTimestamp; + private final byte[] mac; + private final byte[] signature; + + protected IntroduceeAcceptMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, byte[] ephemeralPublicKey, byte[] mac, + byte[] signature, long acceptTimestamp) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + this.ephemeralPublicKey = ephemeralPublicKey; + this.acceptTimestamp = acceptTimestamp; + this.mac = mac; + this.signature = signature; + } + + public SessionId getSessionId() { + return sessionId; + } + + public byte[] getEphemeralPublicKey() { + return ephemeralPublicKey; + } + + public long getAcceptTimestamp() { + return acceptTimestamp; + } + + public byte[] getMac() { + return mac; + } + + public byte[] getSignature() { + return signature; + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java new file mode 100644 index 000000000..78153f39b --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java @@ -0,0 +1,274 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportPropertyManager; +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.briar.api.client.SessionId; +import org.briarproject.briar.api.mailbox.event.MailboxIntroductionRequestReceivedEvent; +import org.briarproject.briar.api.mailbox.event.MailboxIntroductionSucceededEvent; + +import java.security.GeneralSecurityException; +import java.util.Map; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.mailbox.IntroduceeState.AWAIT_AUTH; +import static org.briarproject.briar.mailbox.IntroduceeState.LOCAL_ACCEPTED; +import static org.briarproject.briar.mailbox.IntroduceeState.MAILBOX_ADDED; + +class IntroduceeProtocolEngine + extends AbstractProtocolEngine<IntroduceeSession> { + + private final static Logger LOG = + Logger.getLogger(IntroduceeProtocolEngine.class.getName()); + + @Inject + IntroduceeProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, + ContactManager contactManager, + ContactGroupFactory contactGroupFactory, + IdentityManager identityManager, + MailboxMessageParser messageParser, + MailboxMessageEncoder messageEncoder, Clock clock, + MailboxIntroductionCrypto crypto, KeyManager keyManager, + TransportPropertyManager transportPropertyManager) { + super(db, clientHelper, contactManager, contactGroupFactory, + identityManager, messageParser, messageEncoder, + clock, crypto, keyManager, transportPropertyManager); + } + + @Override + public IntroduceeSession onRequestAction(Transaction txn, + IntroduceeSession session, long timestamp) throws DbException { + return null; + } + + @Override + public IntroduceeSession onAcceptAction(Transaction txn, + IntroduceeSession session, long timestamp) throws DbException { + return null; + } + + @Override + public IntroduceeSession onDeclineAction(Transaction txn, + IntroduceeSession session, long timestamp) throws DbException { + return null; + } + + @Override + public IntroduceeSession onRequestMessage(Transaction txn, + IntroduceeSession session, RequestMessage m) + throws DbException, FormatException { + throw new UnsupportedOperationException(); + } + + @Override + public IntroduceeSession onMailboxAcceptMessage(Transaction txn, + IntroduceeSession session, MailboxAcceptMessage m) + throws DbException, FormatException { + throw new UnsupportedOperationException(); + } + + @Override + public IntroduceeSession onIntroduceeRequestMessage(Transaction txn, + IntroduceeSession session, IntroduceeRequestMessage m) + throws DbException, FormatException { + switch (session.getState()) { + case START: + return onRemoteRequest(txn, session, m); + case AWAIT_LOCAL_RESPONSE: + case LOCAL_DECLINED: + case LOCAL_ACCEPTED: + case AWAIT_REMOTE_RESPONSE: + case MAILBOX_ADDED: + return abort(txn, session); + default: + throw new AssertionError(); + } + } + + private IntroduceeSession onRemoteRequest(Transaction txn, + IntroduceeSession s, + IntroduceeRequestMessage m) throws DbException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getPreviousMessageId())) + return abort(txn, s); + + // Add SessionId to message metadata + addSessionId(txn, m.getMessageId(), s.getSessionId()); + + // Broadcast IntroductionRequestReceivedEvent + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + txn.attach(new MailboxIntroductionRequestReceivedEvent( + localAuthor.getId())); + + // Create ephemeral key pair and get local transport properties + KeyPair keyPair = crypto.generateKeyPair(); + byte[] publicKey = keyPair.getPublic().getEncoded(); + byte[] privateKey = keyPair.getPrivate().getEncoded(); + long localTimestamp = clock.currentTimeMillis(); + try { + SecretKey secretKey = crypto.deriveMasterKey(publicKey, privateKey, + m.getEphemeralPublicKey(), false); + SecretKey aliceMacKey = crypto.deriveMacKey(secretKey, true); + SecretKey bobMacKey = crypto.deriveMacKey(secretKey, false); + + s = IntroduceeSession + .addLocalAccept(s, LOCAL_ACCEPTED, m, publicKey, privateKey, + localTimestamp, secretKey.getBytes(), + aliceMacKey.getBytes(), bobMacKey.getBytes()); + + byte[] mac = crypto.authMac(bobMacKey, s, localAuthor.getId()); + byte[] signature = + crypto.sign(bobMacKey, localAuthor.getPrivateKey()); + + // Send ephemeral public key and timestamp back + Message reply = + sendIntroduceeResponseMessage(txn, s, m.getMessageId(), + localTimestamp, + publicKey, mac, signature, localTimestamp, + s.getMessageCounter()); + //TODO: Check for reasons to decline and if any, move to LOCAL_DECLINE + // Move to the AWAIT_REMOTE_RESPONSE state + return IntroduceeSession + .addLocalAuth(s, AWAIT_AUTH, reply); + } catch (GeneralSecurityException e) { + logException(LOG, WARNING, e); + return abort(txn, s); + } + } + + private IntroduceeSession abort(Transaction txn, IntroduceeSession s) + throws DbException { + /* + // Mark the request message unavailable to answer + markRequestsUnavailableToAnswer(txn, s); + + // Send an ABORT message + Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s)); + + // Broadcast abort event for testing + txn.attach(new IntroductionAbortedEvent(s.getSessionId())); + + // Reset the session back to initial state + return IntroduceeSession.clear(s, START, sent.getId(), + sent.getTimestamp(), s.getLastRemoteMessageId()); + */ + return null; + } + + @Override + public IntroduceeSession onContactAcceptMessage(Transaction txn, + IntroduceeSession session, IntroduceeRequestMessage m) + throws DbException, FormatException { + return null; + } + + @Override + public IntroduceeSession onDeclineMessage(Transaction txn, + IntroduceeSession session, DeclineMessage m) + throws DbException, FormatException { + return null; + } + + @Override + public IntroduceeSession onAuthMessage(Transaction txn, + IntroduceeSession s, + MailboxAuthMessage m) throws DbException, FormatException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s.getLastLocalMessageId(), + m.getPreviousMessageId())) + return abort(txn, s); + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + try { + crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId()); + crypto.verifySignature(m.getSignature(), s); + long timestamp = Math.min(s.getLocal().acceptTimestamp, + s.getRemote().acceptTimestamp); + if (timestamp == -1) throw new AssertionError(); + contactManager + .addContact(txn, s.getRemote().author, localAuthor.getId(), + false, true); + // Only add transport properties and keys when the contact was added + // This will be changed once we have a way to reset state for peers + // that were contacts already at some point in the past. + Contact c = contactManager + .getContact(txn, s.getRemote().author.getId(), + localAuthor.getId()); + // add the keys to the new contact + //noinspection ConstantConditions + Map<TransportId, KeySetId> keys = keyManager + .addContact(txn, c.getId(), new SecretKey(s.masterKey), + timestamp, s.getLocal().alice, true); + // add signed transport properties for the contact + //noinspection ConstantConditions + transportPropertyManager.addRemoteProperties(txn, c.getId(), + m.getTransportProperties()); + // Broadcast MailboxIntroductionSucceededEvent, because contact got added + MailboxIntroductionSucceededEvent e = + new MailboxIntroductionSucceededEvent(c); + txn.attach(e); + } catch (GeneralSecurityException e) { + logException(LOG, WARNING, e); + return abort(txn, s); + } + return IntroduceeSession + .clear(s, MAILBOX_ADDED, s.getLastLocalMessageId(), + clock.currentTimeMillis(), m.getMessageId()); + } + + @Override + public IntroduceeSession onAbortMessage(Transaction txn, + IntroduceeSession session, AbortMessage m) + throws DbException, FormatException { + return null; + } + + @Override + public IntroduceeSession onIntroduceeAcceptMessage(Transaction txn, + IntroduceeSession session, IntroduceeAcceptMessage acceptMessage) { + throw new UnsupportedOperationException(); + } + + + private boolean isInvalidDependency(AbstractIntroduceeSession s, + @Nullable MessageId dependency) { + return isInvalidDependency(s.getLastRemoteMessageId(), dependency); + } + + private long getLocalTimestamp(AbstractIntroduceeSession s) { + return getLocalTimestamp(s.getLocalTimestamp(), + s.getRequestTimestamp()); + } + + private void addSessionId(Transaction txn, MessageId m, SessionId sessionId) + throws DbException { + BdfDictionary meta = new BdfDictionary(); + messageEncoder.addSessionId(meta, sessionId); + try { + clientHelper.mergeMessageMetadata(txn, m, meta); + } catch (FormatException e) { + throw new AssertionError(e); + } + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java new file mode 100644 index 000000000..3e5ea3d3e --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java @@ -0,0 +1,48 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class IntroduceeRequestMessage extends AbstractMailboxIntroductionMessage { + + private final SessionId sessionId; + private final Author author; + private final byte[] ephemeralPublicKey; + private final long acceptTimestamp; + + protected IntroduceeRequestMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, Author author, byte[] ephemeralPublicKey, + long acceptTimestamp) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + this.author = author; + this.ephemeralPublicKey = ephemeralPublicKey; + this.acceptTimestamp = acceptTimestamp; + } + + public SessionId getSessionId() { + return sessionId; + } + + public Author getAuthor() { + return author; + } + + public byte[] getEphemeralPublicKey() { + return ephemeralPublicKey; + } + + public long getAcceptTimestamp() { + return acceptTimestamp; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java new file mode 100644 index 000000000..29270beea --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java @@ -0,0 +1,151 @@ +package org.briarproject.briar.mailbox; + +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.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.briar.api.client.SessionId; +import org.briarproject.briar.api.mailbox.Role; + +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.briar.api.mailbox.Role.INTRODUCEE; +import static org.briarproject.briar.mailbox.IntroduceeState.AWAIT_REMOTE_RESPONSE; +import static org.briarproject.briar.mailbox.IntroduceeState.START; + +@Immutable +@NotNullByDefault +class IntroduceeSession extends AbstractIntroduceeSession<IntroduceeState> { + + + IntroduceeSession(SessionId sessionId, IntroduceeState state, + long requestTimestamp, GroupId contactGroupId, Author introducer, + Local local, Remote remote, @Nullable byte[] masterKey, + @Nullable Map<TransportId, KeySetId> transportKeys, + long sessionCounter) { + super(sessionId, state, requestTimestamp, contactGroupId, introducer, + local, remote, masterKey, transportKeys, sessionCounter); + } + + static IntroduceeSession getInitial(GroupId contactGroupId, + SessionId sessionId, Author introducer, boolean localIsAlice, + Author remoteAuthor) { + Local local = new Local(localIsAlice, null, -1, null, null, -1, null); + Remote remote = + new Remote(!localIsAlice, remoteAuthor, null, null, null, -1, + null); + return new IntroduceeSession(sessionId, START, -1, contactGroupId, + introducer, local, remote, null, null, 0); + } + + static IntroduceeSession addLocalAccept(IntroduceeSession s, + IntroduceeState state, IntroduceeRequestMessage m, + byte[] ephemeralPublicKey, + byte[] ephemeralPrivateKey, long acceptTimestamp, byte[] masterKey, + byte[] aliceMackey, byte[] bobMacKey) { + Local local = new Local(false, s.local.lastMessageId, + s.local.lastMessageTimestamp, + ephemeralPublicKey, ephemeralPrivateKey, acceptTimestamp, + bobMacKey); + Remote remote = new Remote(true, m.getAuthor(), m.getMessageId(), + m.getEphemeralPublicKey(), null, m.getAcceptTimestamp(), + aliceMackey); + return new IntroduceeSession(s.getSessionId(), state, m.getTimestamp(), + s.contactGroupId, s.introducer, local, remote, masterKey, + s.transportKeys, s.getMessageCounter()); + } + + static IntroduceeSession addLocalAuth(IntroduceeSession s, + IntroduceeState state, + Message m) { + // add mac key and sent message + Local local = new Local(false, m.getId(), m.getTimestamp(), + s.local.ephemeralPublicKey, s.local.ephemeralPrivateKey, + s.local.acceptTimestamp, s.local.macKey); + + return new IntroduceeSession(s.getSessionId(), state, + s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, + s.remote, s.masterKey, s.transportKeys, + s.getMessageCounter()); + } + + static IntroduceeSession awaitAuth(IntroduceeSession s, + MailboxAuthMessage m, + Message sent, @Nullable Map<TransportId, KeySetId> 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_REMOTE_RESPONSE, + s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, + remote, null, transportKeys, s.getMessageCounter()); + } + + static IntroduceeSession clear(IntroduceeSession s, IntroduceeState state, + @Nullable MessageId lastLocalMessageId, long localTimestamp, + @Nullable MessageId lastRemoteMessageId) { + Local local = + new Local(s.local.alice, lastLocalMessageId, localTimestamp, + null, null, -1, null); + Remote remote = + new Remote(s.remote.alice, s.remote.author, lastRemoteMessageId, + null, null, -1, null); + return new IntroduceeSession(s.getSessionId(), state, + s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, + remote, null, null, s.getMessageCounter()); + } + + @Override + Role getRole() { + return INTRODUCEE; + } + + @Override + public GroupId getContactGroupId() { + return contactGroupId; + } + + @Override + public long getLocalTimestamp() { + return local.lastMessageTimestamp; + } + + @Nullable + @Override + public MessageId getLastLocalMessageId() { + return local.lastMessageId; + } + + @Nullable + @Override + public MessageId getLastRemoteMessageId() { + return remote.lastMessageId; + } + + Author getIntroducer() { + return introducer; + } + + public Local getLocal() { + return local; + } + + public Remote getRemote() { + return remote; + } + + @Nullable + byte[] getMasterKey() { + return masterKey; + } + + @Nullable + Map<TransportId, KeySetId> getTransportKeys() { + return transportKeys; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeState.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeState.java new file mode 100644 index 000000000..13b2bc316 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeState.java @@ -0,0 +1,36 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +enum IntroduceeState implements State { + + START(0), + AWAIT_LOCAL_RESPONSE(1), + LOCAL_DECLINED(2), + LOCAL_ACCEPTED(3), + AWAIT_REMOTE_RESPONSE(4), + AWAIT_AUTH(5), + MAILBOX_ADDED(6); + + private final int value; + + IntroduceeState(int value) { + this.value = value; + } + + @Override + public int getValue() { + return value; + } + + static IntroduceeState fromValue(int value) throws FormatException { + for (IntroduceeState s : values()) if (s.value == value) return s; + throw new FormatException(); + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroductionConstants.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroductionConstants.java new file mode 100644 index 000000000..3e2d61274 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/IntroductionConstants.java @@ -0,0 +1,49 @@ +package org.briarproject.briar.mailbox; + +interface IntroductionConstants { + + // Group metadata keys + String GROUP_KEY_CONTACT_ID = "contactId"; + + // Message metadata keys + String MSG_KEY_MESSAGE_TYPE = "messageType"; + String MSG_KEY_SESSION_ID = "sessionId"; + String MSG_KEY_TIMESTAMP = "timestamp"; + String MSG_KEY_LOCAL = "local"; + String MSG_KEY_COUNTER = "counter"; + String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer"; + + // Session Keys + String SESSION_KEY_SESSION_ID = "sessionId"; + String SESSION_KEY_COUNTER = "counter"; + String SESSION_KEY_ROLE = "role"; + String SESSION_KEY_STATE = "state"; + String SESSION_KEY_REQUEST_TIMESTAMP = "requestTimestamp"; + String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp"; + String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId"; + String SESSION_KEY_LAST_REMOTE_MESSAGE_ID = "lastRemoteMessageId"; + + // Session Keys Introducer + String SESSION_KEY_INTRODUCEE_A = "introduceeA"; + String SESSION_KEY_INTRODUCEE_B = "introduceeB"; + String SESSION_KEY_GROUP_ID = "groupId"; + String SESSION_KEY_AUTHOR = "author"; + + // Session Keys Introducee + String SESSION_KEY_INTRODUCER = "introducer"; + String SESSION_KEY_LOCAL = "local"; + String SESSION_KEY_REMOTE = "remote"; + + String SESSION_KEY_MASTER_KEY = "masterKey"; + String SESSION_KEY_TRANSPORT_KEYS = "transportKeys"; + + String SESSION_KEY_ALICE = "alice"; + String SESSION_KEY_EPHEMERAL_PUBLIC_KEY = "ephemeralPublicKey"; + String SESSION_KEY_EPHEMERAL_PRIVATE_KEY = "ephemeralPrivateKey"; + String SESSION_KEY_TRANSPORT_PROPERTIES = "transportProperties"; + String SESSION_KEY_ACCEPT_TIMESTAMP = "acceptTimestamp"; + String SESSION_KEY_MAC_KEY = "macKey"; + + String SESSION_KEY_REMOTE_AUTHOR = "remoteAuthor"; + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java new file mode 100644 index 000000000..6b740de82 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java @@ -0,0 +1,41 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class MailboxAcceptMessage extends AbstractMailboxIntroductionMessage { + + private final SessionId sessionId; + private final byte[] ephemeralPublicKey; + private final long acceptTimestamp; + + protected MailboxAcceptMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, byte[] ephemeralPublicKey, + long acceptTimestamp) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + this.ephemeralPublicKey = ephemeralPublicKey; + this.acceptTimestamp = acceptTimestamp; + } + + public SessionId getSessionId() { + return sessionId; + } + + public byte[] getEphemeralPublicKey() { + return ephemeralPublicKey; + } + + public long getAcceptTimestamp() { + return acceptTimestamp; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java new file mode 100644 index 000000000..b66882a70 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxAuthMessage.java @@ -0,0 +1,51 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +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.briar.api.client.SessionId; + +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class MailboxAuthMessage extends AbstractMailboxIntroductionMessage { + + private final SessionId sessionId; + private final Map<TransportId, TransportProperties> transportProperties; + private final byte[] mac; + private final byte[] signature; + + protected MailboxAuthMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, + Map<TransportId, TransportProperties> transportProperties, + byte[] mac, byte[] signature) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + this.transportProperties = transportProperties; + this.mac = mac; + this.signature = signature; + } + + public SessionId getSessionId() { + return sessionId; + } + + public byte[] getMac() { + return mac; + } + + public byte[] getSignature() { + return signature; + } + + public Map<TransportId, TransportProperties> getTransportProperties() { + return transportProperties; + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java new file mode 100644 index 000000000..21a39bfbb --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java @@ -0,0 +1,100 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.AuthorId; +import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.briar.api.client.SessionId; + +import java.security.GeneralSecurityException; + +interface MailboxIntroductionCrypto { + + /** + * Returns the {@link SessionId} based on the introducer + * and the two introducees. + */ + SessionId getSessionId(Author introducer, Author local, Author remote, + boolean isAlice); + + /** + * Generates an agreement key pair. + */ + KeyPair generateKeyPair(); + + /** + * Derives a session master key for Alice or Bob. + * + * @return The secret master key + */ + SecretKey deriveMasterKey(AbstractIntroduceeSession s) + throws GeneralSecurityException; + + @SuppressWarnings("ConstantConditions") + SecretKey deriveMasterKey(byte[] ephemeralPublicKey, + byte[] ephemeralPrivateKey, byte[] remoteEphemeralPublicKey, + boolean alice) + throws GeneralSecurityException; + + /** + * Derives a MAC key from the session's master key for Alice or Bob. + * + * @param masterKey The key returned by {@link #deriveMasterKey(AbstractIntroduceeSession)} + * @param alice true for Alice's MAC key, false for Bob's + * @return The MAC key + */ + SecretKey deriveMacKey(SecretKey masterKey, boolean alice); + + /** + * Generates a MAC that covers both introducee's ephemeral public keys, + * transport properties, Author IDs and timestamps of the accept message. + */ + byte[] authMac(SecretKey macKey, AbstractIntroduceeSession s, + AuthorId localAuthorId); + + /** + * Verifies a received MAC + * + * @param mac The MAC to verify + * as returned by {@link #deriveMasterKey(AbstractIntroduceeSession)} + * @throws GeneralSecurityException if the verification fails + */ + void verifyAuthMac(byte[] mac, AbstractIntroduceeSession s, + AuthorId localAuthorId) + throws GeneralSecurityException; + + /** + * Signs a nonce derived from the macKey + * with the local introducee's identity private key. + * + * @param macKey The corresponding MAC key for the signer's role + * @param privateKey The identity private key + * (from {@link LocalAuthor#getPrivateKey()}) + * @return The signature as a byte array + */ + byte[] sign(SecretKey macKey, byte[] privateKey) + throws GeneralSecurityException; + + /** + * Verifies the signature on a nonce derived from the MAC key. + * + * @throws GeneralSecurityException if the signature is invalid + */ + void verifySignature(byte[] signature, AbstractIntroduceeSession s) + throws GeneralSecurityException; + + /** + * Generates a MAC using the local MAC key. + */ + byte[] activateMac(AbstractIntroduceeSession s); + + /** + * Verifies a MAC from an ACTIVATE message. + * + * @throws GeneralSecurityException if the verification fails + */ + void verifyActivateMac(byte[] mac, AbstractIntroduceeSession s) + throws GeneralSecurityException; + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java new file mode 100644 index 000000000..0de274163 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java @@ -0,0 +1,232 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.KeyParser; +import org.briarproject.bramble.api.crypto.PrivateKey; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.AuthorId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.api.client.SessionId; + +import java.security.GeneralSecurityException; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_ACTIVATE_MAC; +import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_ALICE_MAC_KEY; +import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_MAC; +import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_NONCE; +import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_SIGN; +import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_BOB_MAC_KEY; +import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_MASTER_KEY; +import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID; +import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION; +import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Common; +import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Local; +import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Remote; + +@Immutable +@NotNullByDefault +class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto { + + private final CryptoComponent crypto; + private final ClientHelper clientHelper; + + @Inject + MailboxIntroductionCryptoImpl( + CryptoComponent crypto, + ClientHelper clientHelper) { + this.crypto = crypto; + this.clientHelper = clientHelper; + } + + @Override + public SessionId getSessionId(Author introducer, Author local, + Author remote, boolean isAlice) { + byte[] hash = crypto.hash( + LABEL_SESSION_ID, + introducer.getId().getBytes(), + isAlice ? local.getId().getBytes() : remote.getId().getBytes(), + isAlice ? remote.getId().getBytes() : local.getId().getBytes() + ); + return new SessionId(hash); + } + + @Override + public KeyPair generateKeyPair() { + return crypto.generateAgreementKeyPair(); + } + + @Override + @SuppressWarnings("ConstantConditions") + public SecretKey deriveMasterKey(AbstractIntroduceeSession s) + throws GeneralSecurityException { + return deriveMasterKey( + s.getLocal().ephemeralPublicKey, + s.getLocal().ephemeralPrivateKey, + s.getRemote().ephemeralPublicKey, + s.getLocal().alice + ); + } + + @Override + public SecretKey deriveMasterKey(byte[] publicKey, byte[] privateKey, + byte[] remotePublicKey, boolean alice) + throws GeneralSecurityException { + KeyParser kp = crypto.getAgreementKeyParser(); + PublicKey remoteEphemeralPublicKey = kp.parsePublicKey(remotePublicKey); + PublicKey ephemeralPublicKey = kp.parsePublicKey(publicKey); + PrivateKey ephemeralPrivateKey = kp.parsePrivateKey(privateKey); + KeyPair keyPair = new KeyPair(ephemeralPublicKey, ephemeralPrivateKey); + return crypto.deriveSharedSecret( + LABEL_MASTER_KEY, + remoteEphemeralPublicKey, + keyPair, + new byte[] {MAJOR_VERSION}, + alice ? publicKey : remotePublicKey, + alice ? remotePublicKey : publicKey + ); + } + + @Override + public SecretKey deriveMacKey(SecretKey masterKey, boolean alice) { + return crypto.deriveKey( + alice ? LABEL_ALICE_MAC_KEY : LABEL_BOB_MAC_KEY, + masterKey + ); + } + + @Override + @SuppressWarnings("ConstantConditions") + public byte[] authMac(SecretKey macKey, AbstractIntroduceeSession s, + AuthorId localAuthorId) { + // the macKey is not yet available in the session at this point + return authMac(macKey, s.getIntroducer().getId(), localAuthorId, + s.getLocal(), s.getRemote()); + } + + byte[] authMac(SecretKey macKey, AuthorId introducerId, + AuthorId localAuthorId, Local local, Remote remote) { + byte[] inputs = getAuthMacInputs(introducerId, localAuthorId, local, + remote.author.getId(), remote); + return crypto.mac( + LABEL_AUTH_MAC, + macKey, + inputs + ); + } + + @Override + @SuppressWarnings("ConstantConditions") + public void verifyAuthMac(byte[] mac, AbstractIntroduceeSession s, + AuthorId localAuthorId) throws GeneralSecurityException { + verifyAuthMac(mac, new SecretKey(s.getRemote().macKey), + s.getIntroducer().getId(), localAuthorId, s.getLocal(), + s.getRemote().author.getId(), s.getRemote()); + } + + void verifyAuthMac(byte[] mac, SecretKey macKey, AuthorId introducerId, + AuthorId localAuthorId, Common local, AuthorId remoteAuthorId, + Common remote) throws GeneralSecurityException { + // switch input for verification + byte[] inputs = getAuthMacInputs(introducerId, remoteAuthorId, remote, + localAuthorId, local); + if (!crypto.verifyMac(mac, LABEL_AUTH_MAC, macKey, inputs)) { + throw new GeneralSecurityException(); + } + } + + @SuppressWarnings("ConstantConditions") + private byte[] getAuthMacInputs(AuthorId introducerId, + AuthorId localAuthorId, Common local, AuthorId remoteAuthorId, + Common remote) { + BdfList localInfo = BdfList.of( + localAuthorId, + local.acceptTimestamp, + local.ephemeralPublicKey + ); + BdfList remoteInfo = BdfList.of( + remoteAuthorId, + remote.acceptTimestamp, + remote.ephemeralPublicKey + ); + BdfList macList = BdfList.of( + introducerId, + localInfo, + remoteInfo + ); + try { + return clientHelper.toByteArray(macList); + } catch (FormatException e) { + throw new AssertionError(); + } + } + + @Override + public byte[] sign(SecretKey macKey, byte[] privateKey) + throws GeneralSecurityException { + return crypto.sign( + LABEL_AUTH_SIGN, + getNonce(macKey), + privateKey + ); + } + + @Override + @SuppressWarnings("ConstantConditions") + public void verifySignature(byte[] signature, AbstractIntroduceeSession s) + throws GeneralSecurityException { + SecretKey macKey = new SecretKey(s.getRemote().macKey); + verifySignature(macKey, s.getRemote().author.getPublicKey(), signature); + } + + void verifySignature(SecretKey macKey, byte[] publicKey, + byte[] signature) throws GeneralSecurityException { + byte[] nonce = getNonce(macKey); + if (!crypto.verifySignature(signature, LABEL_AUTH_SIGN, nonce, + publicKey)) { + throw new GeneralSecurityException(); + } + } + + private byte[] getNonce(SecretKey macKey) { + return crypto.mac(LABEL_AUTH_NONCE, macKey); + } + + @Override + public byte[] activateMac(AbstractIntroduceeSession s) { + if (s.getLocal().macKey == null) + throw new AssertionError("Local MAC key is null"); + return activateMac(new SecretKey(s.getLocal().macKey)); + } + + byte[] activateMac(SecretKey macKey) { + return crypto.mac( + LABEL_ACTIVATE_MAC, + macKey + ); + } + + @Override + public void verifyActivateMac(byte[] mac, AbstractIntroduceeSession s) + throws GeneralSecurityException { + if (s.getRemote().macKey == null) + throw new AssertionError("Remote MAC key is null"); + verifyActivateMac(mac, new SecretKey(s.getRemote().macKey)); + } + + void verifyActivateMac(byte[] mac, SecretKey macKey) + throws GeneralSecurityException { + if (!crypto.verifyMac(mac, LABEL_ACTIVATE_MAC, macKey)) { + throw new GeneralSecurityException(); + } + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java new file mode 100644 index 000000000..f7f58897e --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java @@ -0,0 +1,490 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.ContactType; +import org.briarproject.bramble.api.contact.PrivateMailbox; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataParser; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Metadata; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.mailbox.MailboxInfo; +import org.briarproject.bramble.api.sync.Client; +import org.briarproject.bramble.api.sync.Group; +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.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.mailbox.MailboxIntroductionManager; +import org.briarproject.briar.api.mailbox.Role; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static org.briarproject.bramble.api.contact.ContactManager.ContactHook; +import static org.briarproject.bramble.api.contact.ContactType.MAILBOX_OWNER; +import static org.briarproject.bramble.api.contact.ContactType.PRIVATE_MAILBOX; +import static org.briarproject.bramble.api.contact.ContactType.values; +import static org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; +import static org.briarproject.briar.api.mailbox.Role.INTRODUCEE; +import static org.briarproject.briar.api.mailbox.Role.MAILBOX; +import static org.briarproject.briar.api.mailbox.Role.OWNER; +import static org.briarproject.briar.mailbox.IntroductionConstants.GROUP_KEY_CONTACT_ID; +import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_REQUEST; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST; + +class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook + implements MailboxIntroductionManager, Client, ClientVersioningHook, + ContactHook { + + private static final Logger LOG = + Logger.getLogger(MailboxIntroductionManagerImpl.class.getName()); + private final Executor ioExecutor; + private final ClientVersioningManager clientVersioningManager; + private final ContactGroupFactory contactGroupFactory; + private final ContactManager contactManager; + private final MailboxMessageParser messageParser; + // private final SessionEncoder sessionEncoder; + private final MailboxSessionParser sessionParser; + private final OwnerProtocolEngine ownerProtocolEngine; + private final IntroduceeProtocolEngine introduceeProtocolEngine; + private final MailboxSessionEncoder sessionEncoder; + private final MailboxIntroductionCrypto crypto; + private final MailboxProtocolEngine mailboxProtocolEngine; + private final IdentityManager identityManager; + private final Clock clock; + + private final Group localGroup; + + @Inject + MailboxIntroductionManagerImpl(@IoExecutor Executor ioExecutor, + DatabaseComponent db, + ClientHelper clientHelper, + ClientVersioningManager clientVersioningManager, + MetadataParser metadataParser, + ContactGroupFactory contactGroupFactory, + ContactManager contactManager, MailboxMessageParser messageParser, + MailboxSessionParser sessionParser, + OwnerProtocolEngine ownerProtocolEngine, + IntroduceeProtocolEngine introduceeProtocolEngine, + MailboxSessionEncoder sessionEncoder, + MailboxIntroductionCrypto crypto, + MailboxProtocolEngine mailboxProtocolEngine, + IdentityManager identityManager, + Clock clock) { + super(db, clientHelper, metadataParser); + this.ioExecutor = ioExecutor; + this.clientVersioningManager = clientVersioningManager; + this.contactGroupFactory = contactGroupFactory; + this.contactManager = contactManager; + this.messageParser = messageParser; + this.sessionParser = sessionParser; + this.ownerProtocolEngine = ownerProtocolEngine; + this.introduceeProtocolEngine = introduceeProtocolEngine; + this.sessionEncoder = sessionEncoder; + this.crypto = crypto; + this.mailboxProtocolEngine = mailboxProtocolEngine; + this.identityManager = identityManager; + this.localGroup = + contactGroupFactory.createLocalGroup(CLIENT_ID, MAJOR_VERSION); + this.clock = clock; + } + + @Override + public void createLocalState(Transaction txn) throws DbException { + // Create a local group to store protocol sessions + if (db.containsGroup(txn, localGroup.getId())) return; + db.addGroup(txn, localGroup); + // Set up groups for communication with any pre-existing contacts + for (Contact c : db.getAllContacts(txn)) addingContact(txn, c); + } + + @Override + protected boolean incomingMessage(Transaction txn, Message m, BdfList body, + BdfDictionary bdfMeta) throws DbException, FormatException { + // For testing + ContactId introduceeId = getContactId(txn, m.getGroupId()); + ContactType type = db.getContactType(txn, introduceeId); + if (type == MAILBOX_OWNER) { + return incomingMessageMailbox(txn, m, body, bdfMeta); + } else return incomingMessageContact(txn, m, body, bdfMeta); + } + + private boolean incomingMessageMailbox(Transaction txn, Message m, + BdfList body, BdfDictionary bdfMeta) + throws DbException, FormatException { + // Parse the metadata + MessageMetadata meta = messageParser.parseMetadata(bdfMeta); + // Look up the session, if there is one + SessionId sessionId = meta.getSessionId(); + MailboxSession newMailboxSession = null; + if (sessionId == null) { + if (meta.getMessageType() != MAILBOX_REQUEST) + throw new AssertionError(); + newMailboxSession = createNewMailboxSession(txn, m, body); + sessionId = newMailboxSession.getSessionId(); + } + StoredSession ss = getSession(txn, sessionId); + // Handle the message + Session session; + MessageId storageId; + if (ss == null) { + if (meta.getMessageType() != MAILBOX_REQUEST) + throw new FormatException(); + if (newMailboxSession == null) throw new AssertionError(); + storageId = createStorageId(txn); + session = handleMessage(txn, m, body, meta.getMessageType(), + newMailboxSession, mailboxProtocolEngine); + } else { + storageId = ss.storageId; + long messageCounter = + sessionParser.getMessageCounter(ss.bdfSession); + if (meta.getCounter() < messageCounter) { + // TODO: ignore or abort? + throw new FormatException(); + } + Role role = sessionParser.getRole(ss.bdfSession); + if (role == OWNER) { + session = handleMessage(txn, m, body, meta.getMessageType(), + sessionParser.parseOwnerSession(ss.bdfSession), + ownerProtocolEngine); + } else if (role == MAILBOX) { + session = handleMessage(txn, m, body, meta.getMessageType(), + sessionParser.parseMailboxSession(m.getGroupId(), + ss.bdfSession), mailboxProtocolEngine); + } else throw new AssertionError(); + } + // Store the updated session + storeSession(txn, storageId, session); + return false; + } + + private boolean incomingMessageContact(Transaction txn, Message m, + BdfList body, BdfDictionary bdfMeta) + throws DbException, FormatException { + // Parse the metadata + MessageMetadata meta = messageParser.parseMetadata(bdfMeta); + // Look up the session, if there is one + SessionId sessionId = meta.getSessionId(); + IntroduceeSession newMailboxSession = null; + if (sessionId == null) new AssertionError(); + StoredSession ss = getSession(txn, sessionId); + // Handle the message + Session session; + MessageId storageId; + if (ss == null) { + if (meta.getMessageType() == INTRODUCEE_REQUEST) + newMailboxSession = createNewIntroduceeSession(txn, m, body); + if (newMailboxSession == null) throw new AssertionError(); + storageId = createStorageId(txn); + session = handleMessage(txn, m, body, meta.getMessageType(), + newMailboxSession, introduceeProtocolEngine); + } else { + storageId = ss.storageId; + long messageCounter = + sessionParser.getMessageCounter(ss.bdfSession); + if (meta.getCounter() < messageCounter) { + // TODO: ignore or abort? + throw new FormatException(); + } + Role role = sessionParser.getRole(ss.bdfSession); + if (role == OWNER) { + session = handleMessage(txn, m, body, meta.getMessageType(), + sessionParser.parseOwnerSession(ss.bdfSession), + ownerProtocolEngine); + } else if (role == INTRODUCEE) { + session = handleMessage(txn, m, body, meta.getMessageType(), + sessionParser.parseIntroduceeSession(m.getGroupId(), + ss.bdfSession), introduceeProtocolEngine); + } else throw new AssertionError(); + } + // Store the updated session + storeSession(txn, storageId, session); + return false; + } + + private MailboxSession createNewMailboxSession(Transaction txn, Message m, + BdfList body) throws DbException, FormatException { + ContactId ownerId = getContactId(txn, m.getGroupId()); + Author owner = db.getContact(txn, ownerId).getAuthor(); + Author local = identityManager.getLocalAuthor(txn); + Author remote = messageParser.parseRequestMessage(m, body).getAuthor(); + if (local.equals(remote)) throw new FormatException(); + SessionId sessionId = crypto.getSessionId(owner, local, remote, true); + return MailboxSession + .getInitial(m.getGroupId(), sessionId, owner, true, remote); + } + + private IntroduceeSession createNewIntroduceeSession(Transaction txn, + Message m, + BdfList body) + throws DbException, FormatException { + ContactId ownerId = getContactId(txn, m.getGroupId()); + Author owner = db.getContact(txn, ownerId).getAuthor(); + Author local = identityManager.getLocalAuthor(txn); + Author remote = messageParser.parseIntroduceeRequestMessage(m, body) + .getAuthor(); + if (local.equals(remote)) throw new FormatException(); + SessionId sessionId = crypto.getSessionId(owner, local, remote, false); + return IntroduceeSession + .getInitial(m.getGroupId(), sessionId, owner, false, remote); + } + + + /* + private IntroduceeSession createNewIntroduceeSession(Transaction txn, + Message m, BdfList body) throws DbException, FormatException { + ContactId ownerId = getContactId(txn, m.getGroupId()); + Author owner = db.getContact(txn, ownerId).getAuthor(); + Author local = identityManager.getLocalAuthor(txn); + Author remote = messageParser.parseRequestMessage(m, body).getAuthor(); + if (local.equals(remote)) throw new FormatException(); + SessionId sessionId = crypto.getSessionId(owner, local, remote); + boolean alice = crypto.isAlice(local.getId(), remote.getId()); + return IntroduceeSession + .getInitial(m.getGroupId(), sessionId, owner, alice, + remote); + } +*/ + + private <S extends Session> S handleMessage(Transaction txn, Message m, + BdfList body, MessageType type, S session, ProtocolEngine<S> engine) + throws DbException, FormatException { + switch (type) { + + case MAILBOX_REQUEST: { + RequestMessage request = + messageParser.parseRequestMessage(m, body); + return engine.onRequestMessage(txn, session, request); + } + case MAILBOX_ACCEPT: { + MailboxAcceptMessage acceptMessage = + messageParser.parseMailboxAcceptMessage(m, body); + return engine + .onMailboxAcceptMessage(txn, session, acceptMessage); + } + case INTRODUCEE_REQUEST: { + IntroduceeRequestMessage acceptMessage = + messageParser.parseIntroduceeRequestMessage(m, body); + return engine.onIntroduceeRequestMessage(txn, session, + acceptMessage); + } + case INTRODUCEE_ACCEPT: { + IntroduceeAcceptMessage acceptMessage = + messageParser.parseIntroduceeAcceptMessage(m, body); + return engine + .onIntroduceeAcceptMessage(txn, session, acceptMessage); + } + case MAILBOX_AUTH: + MailboxAuthMessage authMessage = + messageParser.parseMailboxAuthMessage(m, body); + return engine.onAuthMessage(txn, session, authMessage); + case ABORT: { + AbortMessage abort = messageParser.parseAbortMessage(m, body); + return engine.onAbortMessage(txn, session, abort); + } + default: + throw new AssertionError(); + } + } + + @Override + public Collection<ContactType> getApplicableContactTypes() { + return Arrays.asList(values()); + } + + @Override + public void addingContact(Transaction txn, Contact c) throws DbException { + switch (c.getType()) { + case PRIVATE_MAILBOX: + break; + case MAILBOX_OWNER: + break; + case CONTACT: + contactAdded(txn, c); + break; + default: + return; + } + } + + @Override + public void contactAdded(Transaction txn, Contact c) throws DbException { + // Create a group to share with the contact + Group g = getContactGroup(c); + db.addGroup(txn, g); + // Apply the client's visibility to the contact group + Group.Visibility client = clientVersioningManager + .getClientVisibility(txn, c.getId(), CLIENT_ID, MAJOR_VERSION); + db.setGroupVisibility(txn, c.getId(), g.getId(), client); + // Attach the contact ID to the group + BdfDictionary meta = new BdfDictionary(); + meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt()); + try { + clientHelper.mergeGroupMetadata(txn, g.getId(), meta); + } catch (FormatException e) { + throw new AssertionError(e); + } + Collection<Contact> pm = db.getContactsByType(txn, PRIVATE_MAILBOX); + if (pm.isEmpty()) return; + Collection<MailboxInfo> mailboxes = db.getContactMailboxes(txn); + for (MailboxInfo mailboxInfo : mailboxes) { + if (mailboxInfo.getContactId().equals(c.getId())) return; + } + ioExecutor.execute( + () -> { + try { + makeIntroduction((PrivateMailbox) pm.iterator().next(), + c, + clock.currentTimeMillis()); + } catch (DbException e) { + LOG.warning( + "Mailbox introduction failed: " + e.toString()); + } + }); + } + + @Override + public void privateMailboxAdded(Transaction txn, + PrivateMailbox privateMailbox) throws DbException { + LOG.info("Private mailbox added"); + } + + @Nullable + private StoredSession getSession(Transaction txn, + @Nullable SessionId sessionId) throws DbException, FormatException { + if (sessionId == null) return null; + BdfDictionary query = sessionParser.getSessionQuery(sessionId); + Map<MessageId, BdfDictionary> results = clientHelper + .getMessageMetadataAsDictionary(txn, localGroup.getId(), query); + if (results.size() > 1) throw new DbException(); + if (results.isEmpty()) return null; + return new StoredSession(results.keySet().iterator().next(), + results.values().iterator().next()); + } + + + @Override + public void makeIntroduction(PrivateMailbox privateMailbox, Contact contact, + long timestamp) throws DbException { + Transaction txn = db.startTransaction(false); + try { + // Look up the session, if there is one + Author introducer = identityManager.getLocalAuthor(txn); + SessionId sessionId = + crypto.getSessionId(introducer, privateMailbox.getAuthor(), + contact.getAuthor(), true); + StoredSession ss = getSession(txn, sessionId); + // Create or parse the session + OwnerSession session; + MessageId storageId; + if (ss == null) { + // This is the first request - create a new session + GroupId groupId1 = getContactGroup(privateMailbox).getId(); + GroupId groupId2 = getContactGroup(contact).getId(); + // use fixed deterministic roles for the introducees + session = new OwnerSession(sessionId, groupId1, + privateMailbox.getAuthor(), groupId2, + contact.getAuthor(), 0); + storageId = createStorageId(txn); + } else { + // An earlier request exists, so we already have a session + session = sessionParser.parseOwnerSession(ss.bdfSession); + storageId = ss.storageId; + } + // Handle the request action + session = ownerProtocolEngine + .onRequestAction(txn, session, timestamp); + // Store the updated session + storeSession(txn, storageId, session); + db.commitTransaction(txn); + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + } + + private void storeSession(Transaction txn, MessageId storageId, + Session session) throws DbException { + BdfDictionary d; + if (session.getRole() == OWNER) { + d = sessionEncoder.encodeIntroducerSession((OwnerSession) session); + } else if (session.getRole() == MAILBOX || + session.getRole() == INTRODUCEE) { + d = sessionEncoder + .encodeIntroduceeSession( + (AbstractIntroduceeSession) session); + } else { + throw new AssertionError(); + } + try { + clientHelper.mergeMessageMetadata(txn, storageId, d); + } catch (FormatException e) { + throw new AssertionError(); + } + } + + private ContactId getContactId(Transaction txn, GroupId contactGroupId) + throws DbException, FormatException { + BdfDictionary meta = + clientHelper.getGroupMetadataAsDictionary(txn, contactGroupId); + return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue()); + } + + @Override + public Group getContactGroup(Contact c) { + return contactGroupFactory + .createContactGroup(CLIENT_ID, MAJOR_VERSION, c); + } + + private MessageId createStorageId(Transaction txn) throws DbException { + Message m = clientHelper + .createMessageForStoringMetadata(localGroup.getId()); + db.addLocalMessage(txn, m, new Metadata(), false); + return m.getId(); + } + + @Override + public void removingContact(Transaction txn, Contact c) throws DbException { + LOG.info("contact removed"); + } + + @Override + public void onClientVisibilityChanging(Transaction txn, Contact c, + Group.Visibility v) throws DbException { + if (!getApplicableContactTypes().contains(c.getType())) return; + // Apply the client's visibility to the contact group + Group g = getContactGroup(c); + db.setGroupVisibility(txn, c.getId(), g.getId(), v); + } + + private static class StoredSession { + + private final MessageId storageId; + private final BdfDictionary bdfSession; + + private StoredSession(MessageId storageId, BdfDictionary bdfSession) { + this.storageId = storageId; + this.bdfSession = bdfSession; + } + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionModule.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionModule.java new file mode 100644 index 000000000..5bf216054 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionModule.java @@ -0,0 +1,96 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.sync.ValidationManager; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.briar.api.introduction.IntroductionManager; +import org.briarproject.briar.api.mailbox.MailboxIntroductionManager; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +import static org.briarproject.briar.api.mailbox.MailboxIntroductionManager.CLIENT_ID; +import static org.briarproject.briar.api.mailbox.MailboxIntroductionManager.MAJOR_VERSION; +import static org.briarproject.briar.api.mailbox.MailboxIntroductionManager.MINOR_VERSION; + +@Module +public class MailboxIntroductionModule { + + public static class EagerSingletons { + @Inject + MailboxIntroductionValidator mailboxIntroductionValidator; + @Inject + MailboxIntroductionManager mailboxIntroductionManager; + } + + + @Provides + @Singleton + MailboxIntroductionValidator provideMailboxValidator( + ValidationManager validationManager, + MailboxMessageEncoder messageEncoder, + MetadataEncoder metadataEncoder, + ClientHelper clientHelper, Clock clock) { + MailboxIntroductionValidator mailboxIntroductionValidator = + new MailboxIntroductionValidator(messageEncoder, clientHelper, + metadataEncoder, clock); + validationManager + .registerMessageValidator(CLIENT_ID, + IntroductionManager.MAJOR_VERSION, + mailboxIntroductionValidator); + return mailboxIntroductionValidator; + } + + @Provides + @Singleton + MailboxIntroductionManager provideMailboxIntroductionManager( + LifecycleManager lifecycleManager, ContactManager contactManager, + ValidationManager validationManager, + ClientVersioningManager clientVersioningManager, + MailboxIntroductionManagerImpl mailboxIntroductionManager) { + lifecycleManager.registerClient(mailboxIntroductionManager); + contactManager.registerContactHook(mailboxIntroductionManager); + validationManager.registerIncomingMessageHook(CLIENT_ID, + MAJOR_VERSION, mailboxIntroductionManager); + clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION, + MINOR_VERSION, mailboxIntroductionManager); + return mailboxIntroductionManager; + } + + @Provides + MailboxMessageParser provideMailboxMessageParser( + MailboxMessageParserImpl messageParser) { + return messageParser; + } + + @Provides + MailboxMessageEncoder provideMailboxMessageEncoder( + MailboxMessageEncoderImpl messageEncoder) { + return messageEncoder; + } + + @Provides + MailboxSessionParser provideMailboxSessionParser( + MailboxSessionParserImpl sessionParser) { + return sessionParser; + } + + @Provides + MailboxSessionEncoder provideMailboxSessionEncoder( + MailboxSessionEncoderImpl sessionEncoder) { + return sessionEncoder; + } + + @Provides + MailboxIntroductionCrypto provideMailboxIntroductionCrypto( + MailboxIntroductionCryptoImpl mailboxIntroductionCrypto) { + return mailboxIntroductionCrypto; + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java new file mode 100644 index 000000000..8725fc9f2 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionValidator.java @@ -0,0 +1,248 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.UniqueId; +import org.briarproject.bramble.api.client.BdfMessageContext; +import org.briarproject.bramble.api.client.BdfMessageValidator; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.briar.api.client.SessionId; + +import java.util.Collections; + +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES; +import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES; +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; +import static org.briarproject.bramble.util.ValidationUtils.checkLength; +import static org.briarproject.bramble.util.ValidationUtils.checkSize; +import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_ACCEPT; +import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_REQUEST; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_ACCEPT; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_AUTH; + + +@Immutable +@NotNullByDefault +class MailboxIntroductionValidator extends BdfMessageValidator { + + private final MailboxMessageEncoder messageEncoder; + + MailboxIntroductionValidator(MailboxMessageEncoder messageEncoder, + ClientHelper clientHelper, MetadataEncoder metadataEncoder, + Clock clock) { + super(clientHelper, metadataEncoder, clock); + this.messageEncoder = messageEncoder; + } + + @Override + protected BdfMessageContext validateMessage(Message m, Group g, + BdfList body) throws FormatException { + MessageType type = MessageType.fromValue(body.getLong(0).intValue()); + + switch (type) { + case MAILBOX_REQUEST: + return validateRequestMessage(m, body); + case MAILBOX_ACCEPT: + return validateAcceptMessage(m, body); + case INTRODUCEE_REQUEST: + return validateIntroduceeRequestMessage(m, body); + case MAILBOX_AUTH: + return validateAuthMessage(m, body); + case INTRODUCEE_ACCEPT: + return validateIntroduceeAcceptMessage(m, body); + case DECLINE: + case ABORT: + return validateOtherMessage(type, m, body); + default: + throw new FormatException(); + } + } + + private BdfMessageContext validateRequestMessage(Message m, BdfList body) + throws FormatException { + checkSize(body, 4); + byte[] previousMessageId = body.getOptionalRaw(1); + checkLength(previousMessageId, UniqueId.LENGTH); + BdfList authorList = body.getList(2); + clientHelper.parseAndValidateAuthor(authorList); + long messageCounter = body.getLong(3); + BdfDictionary meta = messageEncoder + .encodeRequestMetadata(m.getTimestamp(), messageCounter); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + } + + private BdfMessageContext validateAcceptMessage(Message m, BdfList body) + throws FormatException { + checkSize(body, 6); + + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); + + byte[] previousMessageId = body.getOptionalRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + byte[] ephemeralPublicKey = body.getRaw(3); + checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH); + + long timestamp = body.getLong(4); + if (timestamp < 0) throw new FormatException(); + + long messageCounter = body.getLong(5); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(MAILBOX_ACCEPT, sessionId, m.getTimestamp(), + false, messageCounter); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + } + + private BdfMessageContext validateIntroduceeRequestMessage(Message m, + BdfList body) + throws FormatException { + checkSize(body, 7); + + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); + + BdfList authorList = body.getList(2); + clientHelper.parseAndValidateAuthor(authorList); + + byte[] previousMessageId = body.getOptionalRaw(3); + checkLength(previousMessageId, UniqueId.LENGTH); + + byte[] ephemeralPublicKey = body.getRaw(4); + checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH); + + long timestamp = body.getLong(5); + if (timestamp < 0) throw new FormatException(); + + long messageCounter = body.getLong(6); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(INTRODUCEE_REQUEST, sessionId, m.getTimestamp(), + false, messageCounter); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + } + + private BdfMessageContext validateIntroduceeAcceptMessage(Message m, + BdfList body) + throws FormatException { + checkSize(body, 8); + + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); + + byte[] previousMessageId = body.getOptionalRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + byte[] ephemeralPublicKey = body.getRaw(3); + checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH); + + byte[] mac = body.getRaw(4); + checkLength(mac, MAC_BYTES); + + byte[] signature = body.getRaw(5); + checkLength(signature, 1, MAX_SIGNATURE_LENGTH); + + long timestamp = body.getLong(6); + if (timestamp < 0) throw new FormatException(); + + long messageCounter = body.getLong(7); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(INTRODUCEE_ACCEPT, sessionId, m.getTimestamp(), + false, messageCounter); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + } + + private BdfMessageContext validateAuthMessage(Message m, BdfList body) + throws FormatException { + checkSize(body, 7); + + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); + + byte[] previousMessageId = body.getRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + BdfDictionary transportProperties = body.getDictionary(3); + if (transportProperties.size() < 1) throw new FormatException(); + clientHelper + .parseAndValidateTransportPropertiesMap(transportProperties); + + byte[] mac = body.getRaw(4); + checkLength(mac, MAC_BYTES); + + byte[] signature = body.getRaw(5); + checkLength(signature, 1, MAX_SIGNATURE_BYTES); + + long messageCounter = body.getLong(6); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(MAILBOX_AUTH, sessionId, m.getTimestamp(), + false, messageCounter); + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + + private BdfMessageContext validateOtherMessage(MessageType type, Message m, + BdfList body) throws FormatException { + checkSize(body, 3); + + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); + + byte[] previousMessageId = body.getOptionalRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(type, sessionId, m.getTimestamp(), false, 0); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java new file mode 100644 index 000000000..76ab219ed --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java @@ -0,0 +1,66 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.data.BdfDictionary; +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.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.briar.api.client.SessionId; + +import java.util.Map; + +import javax.annotation.Nullable; + +@NotNullByDefault +interface MailboxMessageEncoder { + + BdfDictionary encodeRequestMetadata(long timestamp, long messageCounter); + + BdfDictionary encodeMetadata(MessageType type, + @Nullable SessionId sessionId, long timestamp, boolean local, + long messsageCounter); + + void addSessionId(BdfDictionary meta, SessionId sessionId); + + void setAvailableToAnswer(BdfDictionary meta, boolean available); + + Message encodeRequestMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, Author introduceeAuthor, + long messageCounter); + + Message encodeIntroduceeAcceptMessage(GroupId contactGroupId, + long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId, + byte[] ephemeralPublicKey, byte[] mac, byte[] signature, + long acceptTimestamp, + long messageCounter); + + Message encodeMailboxAcceptMessage(GroupId contactGroupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, MessageType type, byte[] ephemeralPublicKey, + long acceptTimestamp, + long messageCounter); + + Message encodeIntroduceeRequestMessage(GroupId contactGroupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, MessageType type, Author author, + byte[] ephemeralPublicKey, long acceptTimestamp, + long messageCounter); + + Message encodeMailboxAuthMessage(GroupId contactGroupId, + long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId, + Map<TransportId, TransportProperties> transportProperties, + byte[] mac, byte[] signature, + long messageCounter); + + Message encodeDeclineMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId); + + Message encodeAbortMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId); + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java new file mode 100644 index 000000000..6fe872150 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java @@ -0,0 +1,172 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +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.properties.TransportProperties; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageFactory; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import java.util.Map; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_COUNTER; +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_LOCAL; +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_SESSION_ID; +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_TIMESTAMP; +import static org.briarproject.briar.mailbox.MessageType.ABORT; +import static org.briarproject.briar.mailbox.MessageType.DECLINE; +import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_ACCEPT; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_AUTH; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST; + +@NotNullByDefault +class MailboxMessageEncoderImpl implements MailboxMessageEncoder { + + private final ClientHelper clientHelper; + private final MessageFactory messageFactory; + + @Inject + MailboxMessageEncoderImpl(ClientHelper clientHelper, + MessageFactory messageFactory) { + this.clientHelper = clientHelper; + this.messageFactory = messageFactory; + } + + @Override + public BdfDictionary encodeRequestMetadata(long timestamp, + long messageCounter) { + BdfDictionary meta = + encodeMetadata(MAILBOX_REQUEST, null, timestamp, false, + messageCounter); + meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false); + return meta; + } + + @Override + public BdfDictionary encodeMetadata(MessageType type, + @Nullable SessionId sessionId, long timestamp, boolean local, + long messageCounter) { + BdfDictionary meta = new BdfDictionary(); + meta.put(MSG_KEY_MESSAGE_TYPE, type.getValue()); + if (sessionId != null) meta.put(MSG_KEY_SESSION_ID, sessionId); + else if (type != MAILBOX_REQUEST) throw new IllegalArgumentException(); + meta.put(MSG_KEY_TIMESTAMP, timestamp); + meta.put(MSG_KEY_LOCAL, local); + meta.put(MSG_KEY_COUNTER, messageCounter); + return meta; + } + + @Override + public void addSessionId(BdfDictionary meta, SessionId sessionId) { + meta.put(MSG_KEY_SESSION_ID, sessionId); + } + + @Override + public void setAvailableToAnswer(BdfDictionary meta, boolean available) { + meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available); + } + + @Override + public Message encodeRequestMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, Author introduceeAuthor, + long messageCounter) { + BdfList body = BdfList.of(MAILBOX_REQUEST.getValue(), previousMessageId, + clientHelper.toList(introduceeAuthor), messageCounter); + return createMessage(contactGroupId, timestamp, body); + } + + @Override + public Message encodeMailboxAcceptMessage(GroupId contactGroupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, MessageType type, + byte[] ephemeralPublicKey, + long acceptTimestamp, long messageCounter) { + BdfList body = BdfList.of(type.getValue(), sessionId, + previousMessageId, ephemeralPublicKey, acceptTimestamp, + messageCounter); + return createMessage(contactGroupId, timestamp, body); + } + + @Override + public Message encodeIntroduceeRequestMessage(GroupId contactGroupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, MessageType type, Author author, + byte[] ephemeralPublicKey, + long acceptTimestamp, long messageCounter) { + BdfList body = BdfList.of(type.getValue(), sessionId, + clientHelper.toList(author), + previousMessageId, ephemeralPublicKey, acceptTimestamp, + messageCounter); + return createMessage(contactGroupId, timestamp, body); + } + + @Override + public Message encodeIntroduceeAcceptMessage(GroupId contactGroupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, byte[] ephemeralPublicKey, byte[] mac, + byte[] signature, long acceptTimestamp, long messageCounter) { + BdfList body = BdfList.of(INTRODUCEE_ACCEPT.getValue(), sessionId, + previousMessageId, ephemeralPublicKey, mac, signature, + acceptTimestamp, + messageCounter); + return createMessage(contactGroupId, timestamp, body); + } + + @Override + public Message encodeMailboxAuthMessage(GroupId contactGroupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, + Map<TransportId, TransportProperties> transportProperties, + byte[] mac, + byte[] signature, long messageCounter) { + BdfList body = BdfList.of(MAILBOX_AUTH.getValue(), sessionId, + previousMessageId, clientHelper.toDictionary(transportProperties), mac, signature, + messageCounter); + return createMessage(contactGroupId, timestamp, body); + } + + @Override + public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId) { + return encodeMessage(DECLINE, contactGroupId, sessionId, timestamp, + previousMessageId); + } + + @Override + public Message encodeAbortMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId) { + return encodeMessage(ABORT, contactGroupId, sessionId, timestamp, + previousMessageId); + } + + private Message encodeMessage(MessageType type, GroupId contactGroupId, + SessionId sessionId, long timestamp, + @Nullable MessageId previousMessageId) { + BdfList body = + BdfList.of(type.getValue(), sessionId, previousMessageId); + return createMessage(contactGroupId, timestamp, body); + } + + private Message createMessage(GroupId contactGroupId, long timestamp, + BdfList body) { + try { + return messageFactory.createMessage(contactGroupId, timestamp, + clientHelper.toByteArray(body)); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java new file mode 100644 index 000000000..4c928a78d --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java @@ -0,0 +1,40 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.briar.api.client.SessionId; + +@NotNullByDefault +interface MailboxMessageParser { + + BdfDictionary getRequestsAvailableToAnswerQuery(SessionId sessionId); + + MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException; + + //MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException; + + RequestMessage parseRequestMessage(Message m, BdfList body) + throws FormatException; + + MailboxAcceptMessage parseMailboxAcceptMessage(Message m, BdfList body) + throws FormatException; + + IntroduceeRequestMessage parseIntroduceeRequestMessage(Message m, + BdfList body) throws FormatException; + + IntroduceeAcceptMessage parseIntroduceeAcceptMessage(Message m, + BdfList body) throws FormatException; + + DeclineMessage parseDeclineMessage(Message m, BdfList body) + throws FormatException; + + MailboxAuthMessage parseMailboxAuthMessage(Message m, BdfList body) + throws FormatException; + + AbortMessage parseAbortMessage(Message m, BdfList body) + throws FormatException; + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java new file mode 100644 index 000000000..a040aa3d7 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java @@ -0,0 +1,156 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +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.properties.TransportProperties; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import java.util.Map; + +import javax.inject.Inject; + +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_COUNTER; +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_LOCAL; +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_SESSION_ID; +import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_TIMESTAMP; +import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST; + +@NotNullByDefault +class MailboxMessageParserImpl implements MailboxMessageParser { + + private final ClientHelper clientHelper; + + @Inject + MailboxMessageParserImpl(ClientHelper clientHelper) { + this.clientHelper = clientHelper; + } + + @Override + public BdfDictionary getRequestsAvailableToAnswerQuery( + SessionId sessionId) { + return BdfDictionary.of(new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true), + new BdfEntry(MSG_KEY_MESSAGE_TYPE, MAILBOX_REQUEST.getValue()), + new BdfEntry(MSG_KEY_SESSION_ID, sessionId)); + } + + @Override + public MessageMetadata parseMetadata(BdfDictionary d) + throws FormatException { + MessageType type = MessageType + .fromValue(d.getLong(MSG_KEY_MESSAGE_TYPE).intValue()); + byte[] sessionIdBytes = d.getOptionalRaw(MSG_KEY_SESSION_ID); + SessionId sessionId = + sessionIdBytes == null ? null : new SessionId(sessionIdBytes); + long timestamp = d.getLong(MSG_KEY_TIMESTAMP); + boolean local = d.getBoolean(MSG_KEY_LOCAL); + long counter = d.getLong(MSG_KEY_COUNTER); + boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false); + return new MessageMetadata(type, sessionId, timestamp, local, counter, + available); + } + + @Override + public RequestMessage parseRequestMessage(Message m, BdfList body) + throws FormatException { + byte[] previousMsgBytes = body.getOptionalRaw(1); + MessageId previousMessageId = (previousMsgBytes == null ? null : + new MessageId(previousMsgBytes)); + Author author = clientHelper.parseAndValidateAuthor(body.getList(2)); + return new RequestMessage(m.getId(), m.getGroupId(), m.getTimestamp(), + previousMessageId, author); + } + + @Override + public MailboxAcceptMessage parseMailboxAcceptMessage(Message m, + BdfList body) throws FormatException { + SessionId sessionId = new SessionId(body.getRaw(1)); + byte[] previousMsgBytes = body.getOptionalRaw(2); + MessageId previousMessageId = (previousMsgBytes == null ? null : + new MessageId(previousMsgBytes)); + byte[] ephemeralPublicKey = body.getRaw(3); + long acceptTimestamp = body.getLong(4); + return new MailboxAcceptMessage(m.getId(), m.getGroupId(), + m.getTimestamp(), previousMessageId, sessionId, + ephemeralPublicKey, acceptTimestamp); + } + + @Override + public IntroduceeRequestMessage parseIntroduceeRequestMessage(Message m, + BdfList body) throws FormatException { + SessionId sessionId = new SessionId(body.getRaw(1)); + Author author = clientHelper.parseAndValidateAuthor(body.getList(2)); + byte[] previousMsgBytes = body.getOptionalRaw(3); + MessageId previousMessageId = (previousMsgBytes == null ? null : + new MessageId(previousMsgBytes)); + byte[] ephemeralPublicKey = body.getRaw(4); + long acceptTimestamp = body.getLong(5); + return new IntroduceeRequestMessage(m.getId(), m.getGroupId(), + m.getTimestamp(), previousMessageId, sessionId, author, + ephemeralPublicKey, acceptTimestamp); + } + + @Override + public IntroduceeAcceptMessage parseIntroduceeAcceptMessage(Message m, + BdfList body) throws FormatException { + SessionId sessionId = new SessionId(body.getRaw(1)); + byte[] previousMsgBytes = body.getOptionalRaw(2); + MessageId previousMessageId = (previousMsgBytes == null ? null : + new MessageId(previousMsgBytes)); + byte[] ephemeralPublicKey = body.getRaw(3); + byte[] mac = body.getRaw(4); + byte[] signature = body.getRaw(5); + long acceptTimestamp = body.getLong(6); + return new IntroduceeAcceptMessage(m.getId(), m.getGroupId(), + m.getTimestamp(), previousMessageId, sessionId, + ephemeralPublicKey, mac, signature, acceptTimestamp); + } + + @Override + public DeclineMessage parseDeclineMessage(Message m, BdfList body) + throws FormatException { + SessionId sessionId = new SessionId(body.getRaw(1)); + byte[] previousMsgBytes = body.getOptionalRaw(2); + MessageId previousMessageId = (previousMsgBytes == null ? null : + new MessageId(previousMsgBytes)); + return new DeclineMessage(m.getId(), m.getGroupId(), m.getTimestamp(), + previousMessageId, sessionId); + } + + @Override + public MailboxAuthMessage parseMailboxAuthMessage(Message m, BdfList body) + throws FormatException { + SessionId sessionId = new SessionId(body.getRaw(1)); + byte[] previousMsgBytes = body.getRaw(2); + MessageId previousMessageId = new MessageId(previousMsgBytes); + Map<TransportId, TransportProperties> transportProperties = clientHelper + .parseAndValidateTransportPropertiesMap(body.getDictionary(3)); + byte[] mac = body.getRaw(4); + byte[] signature = body.getRaw(5); + return new MailboxAuthMessage(m.getId(), m.getGroupId(), + m.getTimestamp(), + previousMessageId, sessionId, transportProperties, mac, + signature); + } + + @Override + public AbortMessage parseAbortMessage(Message m, BdfList body) + throws FormatException { + SessionId sessionId = new SessionId(body.getRaw(1)); + byte[] previousMsgBytes = body.getOptionalRaw(2); + MessageId previousMessageId = (previousMsgBytes == null ? null : + new MessageId(previousMsgBytes)); + return new AbortMessage(m.getId(), m.getGroupId(), m.getTimestamp(), + previousMessageId, sessionId); + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java new file mode 100644 index 000000000..ea128eb7d --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java @@ -0,0 +1,278 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.bramble.api.properties.TransportPropertyManager; +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.briar.api.client.SessionId; +import org.briarproject.briar.api.mailbox.event.MailboxIntroductionRequestReceivedEvent; +import org.briarproject.briar.api.mailbox.event.MailboxIntroductionSucceededEvent; + +import java.security.GeneralSecurityException; +import java.util.Map; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.mailbox.MailboxState.AWAIT_REMOTE_RESPONSE; +import static org.briarproject.briar.mailbox.MailboxState.CONTACT_ADDED; + +class MailboxProtocolEngine extends AbstractProtocolEngine<MailboxSession> { + + private final static Logger LOG = + Logger.getLogger(MailboxProtocolEngine.class.getName()); + + @Inject + MailboxProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, + ContactManager contactManager, + ContactGroupFactory contactGroupFactory, + IdentityManager identityManager, + MailboxMessageParser messageParser, + MailboxMessageEncoder messageEncoder, Clock clock, + MailboxIntroductionCrypto crypto, + KeyManager keyManager, + TransportPropertyManager transportPropertyManager) { + super(db, clientHelper, contactManager, contactGroupFactory, + identityManager, messageParser, messageEncoder, + clock, crypto, keyManager, transportPropertyManager); + } + + @Override + public MailboxSession onRequestAction(Transaction txn, + MailboxSession session, long timestamp) throws DbException { + return null; + } + + @Override + public MailboxSession onAcceptAction(Transaction txn, + MailboxSession session, long timestamp) throws DbException { + return null; + } + + @Override + public MailboxSession onDeclineAction(Transaction txn, + MailboxSession session, long timestamp) throws DbException { + return null; + } + + @Override + public MailboxSession onRequestMessage(Transaction txn, + MailboxSession session, RequestMessage m) + throws DbException, FormatException { + switch (session.getState()) { + case START: + return onRemoteRequest(txn, session, m); + case AWAIT_LOCAL_RESPONSE: + case LOCAL_DECLINED: + case LOCAL_ACCEPTED: + case AWAIT_REMOTE_RESPONSE: + case CONTACT_ADDED: + return abort(txn, session); + default: + throw new AssertionError(); + } + } + + private MailboxSession onRemoteRequest(Transaction txn, MailboxSession s, + RequestMessage m) throws DbException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getPreviousMessageId())) + return abort(txn, s); + + // Add SessionId to message metadata + addSessionId(txn, m.getMessageId(), s.getSessionId()); + + // Broadcast IntroductionRequestReceivedEvent + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + txn.attach(new MailboxIntroductionRequestReceivedEvent( + localAuthor.getId())); + + // Create ephemeral key pair and get local transport properties + KeyPair keyPair = crypto.generateKeyPair(); + byte[] publicKey = keyPair.getPublic().getEncoded(); + byte[] privateKey = keyPair.getPrivate().getEncoded(); + long localTimestamp = clock.currentTimeMillis(); + // Send ephemeral public key and timestamp back + Message reply = + sendMailboxAcceptMessage(txn, s, localTimestamp, publicKey, + localTimestamp, s.getMessageCounter()); + + //TODO: Check for reasons to decline and if any, move to LOCAL_DECLINE + // Move to the AWAIT_REMOTE_RESPONSE state + return MailboxSession + .addLocalAccept(s, AWAIT_REMOTE_RESPONSE, reply, + publicKey, + privateKey, localTimestamp); + } + + private MailboxSession abort(Transaction txn, MailboxSession s) + throws DbException { + /* + // Mark the request message unavailable to answer + markRequestsUnavailableToAnswer(txn, s); + + // Send an ABORT message + Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s)); + + // Broadcast abort event for testing + txn.attach(new IntroductionAbortedEvent(s.getSessionId())); + + // Reset the session back to initial state + return IntroduceeSession.clear(s, START, sent.getId(), + sent.getTimestamp(), s.getLastRemoteMessageId()); + */ + return null; + } + + @Override + public MailboxSession onMailboxAcceptMessage(Transaction txn, + MailboxSession session, MailboxAcceptMessage m) + throws DbException, FormatException { + return null; + } + + @Override + public MailboxSession onIntroduceeRequestMessage(Transaction txn, + MailboxSession session, IntroduceeRequestMessage m) + throws DbException, FormatException { + throw new UnsupportedOperationException(); + } + + @Override + public MailboxSession onContactAcceptMessage(Transaction txn, + MailboxSession session, IntroduceeRequestMessage m) + throws DbException, FormatException { + return null; + } + + @Override + public MailboxSession onDeclineMessage(Transaction txn, + MailboxSession session, DeclineMessage m) + throws DbException, FormatException { + return null; + } + + @Override + public MailboxSession onAuthMessage(Transaction txn, MailboxSession session, + MailboxAuthMessage m) throws DbException, FormatException { + return null; + } + + @Override + public MailboxSession onAbortMessage(Transaction txn, + MailboxSession session, AbortMessage m) + throws DbException, FormatException { + return null; + } + + @Override + public MailboxSession onIntroduceeAcceptMessage(Transaction txn, + MailboxSession s, IntroduceeAcceptMessage m) throws DbException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s.getLastLocalMessageId(), + m.getPreviousMessageId())) + return abort(txn, s); + // Broadcast IntroductionRequestReceivedEvent + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + try { + SecretKey secretKey = + crypto.deriveMasterKey(s.local.ephemeralPublicKey, + s.local.ephemeralPrivateKey, + m.getEphemeralPublicKey(), true); + SecretKey aliceMacKey = crypto.deriveMacKey(secretKey, true); + SecretKey bobMacKey = crypto.deriveMacKey(secretKey, false); + + s = MailboxSession.addIntroduceeAccept(s, m.getMessageId(), + m.getEphemeralPublicKey(), secretKey, aliceMacKey, + bobMacKey, + m.getAcceptTimestamp()); + crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId()); + crypto.verifySignature(m.getSignature(), s); + long timestamp = Math.min(s.getLocal().acceptTimestamp, + s.getRemote().acceptTimestamp); + if (timestamp == -1) throw new AssertionError(); + byte[] mac = crypto.authMac(aliceMacKey, s, localAuthor.getId()); + byte[] signature = + crypto.sign(aliceMacKey, localAuthor.getPrivateKey()); + + contactManager + .addContact(txn, s.getRemote().author, localAuthor.getId(), + false, true); + // Only add transport properties and keys when the contact was added + // This will be changed once we have a way to reset state for peers + // that were contacts already at some point in the past. + Contact c = contactManager + .getContact(txn, s.getRemote().author.getId(), + localAuthor.getId()); + // add the keys to the new contact + //noinspection ConstantConditions + Map<TransportId, KeySetId> keys = keyManager + .addContact(txn, c.getId(), secretKey, + timestamp, s.getLocal().alice, true); + // add signed transport properties for the contact + //noinspection ConstantConditions + transportPropertyManager.addRemoteProperties(txn, c.getId(), + transportPropertyManager.getLocalAnonymizedProperties(txn)); + // Broadcast MailboxIntroductionSucceededEvent, because contact got added + MailboxIntroductionSucceededEvent e = + new MailboxIntroductionSucceededEvent(c); + txn.attach(e); + Map<TransportId, TransportProperties> transportProperties = + transportPropertyManager.getLocalProperties(txn); + Message reply = + sendMailboxAuthMessage(txn, s, clock.currentTimeMillis(), + transportProperties, mac, signature, + s.getMessageCounter()); + //TODO: Check for reasons to decline and if any, move to LOCAL_DECLINE + return MailboxSession + .clear(s, CONTACT_ADDED, reply.getId(), + s.getLocalTimestamp(), m.getMessageId()); + } catch (GeneralSecurityException e) { + logException(LOG, WARNING, e); + return abort(txn, s); + } + + } + + + private boolean isInvalidDependency(AbstractIntroduceeSession s, + @Nullable MessageId dependency) { + return isInvalidDependency(s.getLastRemoteMessageId(), dependency); + } + + private long getLocalTimestamp(AbstractIntroduceeSession s) { + return getLocalTimestamp(s.getLocalTimestamp(), + s.getRequestTimestamp()); + } + + private void addSessionId(Transaction txn, MessageId m, SessionId sessionId) + throws DbException { + BdfDictionary meta = new BdfDictionary(); + messageEncoder.addSessionId(meta, sessionId); + try { + clientHelper.mergeMessageMetadata(txn, m, meta); + } catch (FormatException e) { + throw new AssertionError(e); + } + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java new file mode 100644 index 000000000..a9a75c8b6 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java @@ -0,0 +1,139 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.crypto.SecretKey; +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.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.briar.api.client.SessionId; +import org.briarproject.briar.api.mailbox.Role; + +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.briar.api.mailbox.Role.MAILBOX; +import static org.briarproject.briar.mailbox.MailboxState.START; + +@Immutable +@NotNullByDefault +class MailboxSession extends AbstractIntroduceeSession<MailboxState> { + + + MailboxSession(SessionId sessionId, MailboxState state, + long requestTimestamp, GroupId contactGroupId, Author introducer, + Local local, Remote remote, @Nullable byte[] masterKey, + @Nullable Map<TransportId, KeySetId> transportKeys, + long sessionCounter) { + super(sessionId, state, requestTimestamp, contactGroupId, introducer, + local, remote, masterKey, transportKeys, sessionCounter); + } + + static MailboxSession getInitial(GroupId contactGroupId, + SessionId sessionId, Author introducer, boolean localIsAlice, + Author remoteAuthor) { + Local local = new Local(localIsAlice, null, -1, null, null, -1, null); + Remote remote = + new Remote(!localIsAlice, remoteAuthor, null, null, null, -1, + null); + return new MailboxSession(sessionId, START, -1, + contactGroupId, + introducer, local, remote, null, null, 0); + } + + static MailboxSession addLocalAccept(MailboxSession s, + MailboxState state, Message m, byte[] ephemeralPublicKey, + byte[] ephemeralPrivateKey, long acceptTimestamp) { + Local local = new Local(s.local.alice, m.getId(), m.getTimestamp(), + ephemeralPublicKey, ephemeralPrivateKey, acceptTimestamp, null); + return new MailboxSession(s.getSessionId(), state, m.getTimestamp(), + s.contactGroupId, s.introducer, local, s.remote, s.masterKey, + s.transportKeys, s.getMessageCounter()); + } + + static MailboxSession clear(MailboxSession s, MailboxState state, + @Nullable MessageId lastLocalMessageId, long localTimestamp, + @Nullable MessageId lastRemoteMessageId) { + Local local = + new Local(s.local.alice, lastLocalMessageId, localTimestamp, + null, null, -1, null); + Remote remote = + new Remote(s.remote.alice, s.remote.author, lastRemoteMessageId, + null, null, -1, null); + return new MailboxSession(s.getSessionId(), state, + s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, + remote, null, null, s.getMessageCounter()); + } + + static MailboxSession addIntroduceeAccept(MailboxSession s, + MessageId lastMessageId, + byte[] ephemeralPublicKey, SecretKey masterKey, + SecretKey aliceMacKey, + SecretKey bobMacKey, long acceptTimestamp) { + Local local = new Local(s.local.alice, s.local.lastMessageId, + s.local.lastMessageTimestamp, s.local.ephemeralPublicKey, + s.local.ephemeralPrivateKey, s.local.acceptTimestamp, + aliceMacKey.getBytes()); + Remote remote = + new Remote(false, s.remote.author, lastMessageId, + ephemeralPublicKey, null, acceptTimestamp, + bobMacKey.getBytes()); + return new MailboxSession(s.getSessionId(), s.getState(), + s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, + remote, masterKey.getBytes(), null, s.getMessageCounter()); + } + + @Override + Role getRole() { + return MAILBOX; + } + + @Override + public GroupId getContactGroupId() { + return contactGroupId; + } + + @Override + public long getLocalTimestamp() { + return local.lastMessageTimestamp; + } + + @Nullable + @Override + public MessageId getLastLocalMessageId() { + return local.lastMessageId; + } + + @Nullable + @Override + public MessageId getLastRemoteMessageId() { + return remote.lastMessageId; + } + + Author getIntroducer() { + return introducer; + } + + public Local getLocal() { + return local; + } + + public Remote getRemote() { + return remote; + } + + @Nullable + byte[] getMasterKey() { + return masterKey; + } + + @Nullable + Map<TransportId, KeySetId> getTransportKeys() { + return transportKeys; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java new file mode 100644 index 000000000..a20028f41 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java @@ -0,0 +1,18 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@NotNullByDefault +interface MailboxSessionEncoder { + + BdfDictionary getIntroduceeSessionsByIntroducerQuery(Author introducer); + + BdfDictionary getIntroducerSessionsQuery(); + + BdfDictionary encodeIntroducerSession(OwnerSession s); + + BdfDictionary encodeIntroduceeSession(AbstractIntroduceeSession s); + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java new file mode 100644 index 000000000..af188a754 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java @@ -0,0 +1,157 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +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 java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; +import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; +import static org.briarproject.briar.api.introduction.Role.INTRODUCER; +import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Common; +import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Local; +import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Remote; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ALICE; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_AUTHOR; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_COUNTER; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_GROUP_ID; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_INTRODUCEE_A; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_INTRODUCEE_B; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_INTRODUCER; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_LOCAL; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_MAC_KEY; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_MASTER_KEY; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_REMOTE; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ROLE; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_SESSION_ID; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_STATE; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; + +@Immutable +@NotNullByDefault +class MailboxSessionEncoderImpl implements + MailboxSessionEncoder { + + private final ClientHelper clientHelper; + + @Inject + MailboxSessionEncoderImpl(ClientHelper clientHelper) { + this.clientHelper = clientHelper; + } + + @Override + public BdfDictionary getIntroduceeSessionsByIntroducerQuery( + Author introducer) { + return BdfDictionary.of( + new BdfEntry(SESSION_KEY_ROLE, INTRODUCEE.getValue()), + new BdfEntry(SESSION_KEY_INTRODUCER, + clientHelper.toList(introducer)) + ); + } + + @Override + public BdfDictionary getIntroducerSessionsQuery() { + return BdfDictionary.of( + new BdfEntry(SESSION_KEY_ROLE, INTRODUCER.getValue()) + ); + } + + @Override + public BdfDictionary encodeIntroducerSession(OwnerSession s) { + BdfDictionary d = encodeSession(s); + d.put(SESSION_KEY_INTRODUCEE_A, encodeIntroducee(s.getMailbox())); + d.put(SESSION_KEY_INTRODUCEE_B, encodeIntroducee(s.getIntroducee())); + return d; + } + + private BdfDictionary encodeIntroducee(Introducee i) { + BdfDictionary d = new BdfDictionary(); + putNullable(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID, i.lastLocalMessageId); + putNullable(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID, + i.lastRemoteMessageId); + d.put(SESSION_KEY_LOCAL_TIMESTAMP, i.localTimestamp); + d.put(SESSION_KEY_GROUP_ID, i.groupId); + d.put(SESSION_KEY_AUTHOR, clientHelper.toList(i.author)); + return d; + } + + @Override + public BdfDictionary encodeIntroduceeSession(AbstractIntroduceeSession s) { + BdfDictionary d = encodeSession(s); + d.put(SESSION_KEY_INTRODUCER, clientHelper.toList(s.getIntroducer())); + d.put(SESSION_KEY_LOCAL, encodeLocal(s.getLocal())); + d.put(SESSION_KEY_REMOTE, encodeRemote(s.getRemote())); + putNullable(d, SESSION_KEY_MASTER_KEY, s.getMasterKey()); + putNullable(d, SESSION_KEY_TRANSPORT_KEYS, + encodeTransportKeys(s.getTransportKeys())); + return d; + } + + private BdfDictionary encodeCommon(Common s) { + BdfDictionary d = new BdfDictionary(); + d.put(SESSION_KEY_ALICE, s.alice); + putNullable(d, SESSION_KEY_EPHEMERAL_PUBLIC_KEY, s.ephemeralPublicKey); + d.put(SESSION_KEY_ACCEPT_TIMESTAMP, s.acceptTimestamp); + putNullable(d, SESSION_KEY_MAC_KEY, s.macKey); + return d; + } + + private BdfDictionary encodeLocal(Local s) { + BdfDictionary d = encodeCommon(s); + d.put(SESSION_KEY_LOCAL_TIMESTAMP, s.lastMessageTimestamp); + putNullable(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID, s.lastMessageId); + putNullable(d, SESSION_KEY_EPHEMERAL_PRIVATE_KEY, + s.ephemeralPrivateKey); + return d; + } + + private BdfDictionary encodeRemote(Remote s) { + BdfDictionary d = encodeCommon(s); + d.put(SESSION_KEY_REMOTE_AUTHOR, clientHelper.toList(s.author)); + putNullable(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID, s.lastMessageId); + return d; + } + + private BdfDictionary encodeSession(Session s) { + BdfDictionary d = new BdfDictionary(); + d.put(SESSION_KEY_SESSION_ID, s.getSessionId()); + d.put(SESSION_KEY_ROLE, s.getRole().getValue()); + d.put(SESSION_KEY_STATE, s.getState().getValue()); + d.put(SESSION_KEY_REQUEST_TIMESTAMP, s.getRequestTimestamp()); + d.put(SESSION_KEY_COUNTER, s.getMessageCounter()); + return d; + } + + @Nullable + private BdfDictionary encodeTransportKeys( + @Nullable Map<TransportId, KeySetId> keys) { + if (keys == null) return null; + BdfDictionary d = new BdfDictionary(); + for (Map.Entry<TransportId, KeySetId> e : keys.entrySet()) { + d.put(e.getKey().getString(), e.getValue().getInt()); + } + return d; + } + + private void putNullable(BdfDictionary d, String key, @Nullable Object o) { + d.put(key, o == null ? NULL_VALUE : o); + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java new file mode 100644 index 000000000..b3b439dd8 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParser.java @@ -0,0 +1,28 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.mailbox.Role; + +@NotNullByDefault +interface MailboxSessionParser { + + BdfDictionary getSessionQuery(SessionId s); + + Role getRole(BdfDictionary d) throws FormatException; + + long getMessageCounter(BdfDictionary d) throws FormatException; + + OwnerSession parseOwnerSession(BdfDictionary d) + throws FormatException; + + MailboxSession parseMailboxSession(GroupId introducerGroupId, + BdfDictionary d) throws FormatException; + + IntroduceeSession parseIntroduceeSession(GroupId introducerGroupId, + BdfDictionary d) throws FormatException; + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java new file mode 100644 index 000000000..e74ee5238 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java @@ -0,0 +1,230 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +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.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.briar.api.client.SessionId; +import org.briarproject.briar.api.mailbox.Role; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.briar.api.mailbox.Role.INTRODUCEE; +import static org.briarproject.briar.api.mailbox.Role.MAILBOX; +import static org.briarproject.briar.api.mailbox.Role.OWNER; +import static org.briarproject.briar.api.mailbox.Role.fromValue; +import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Local; +import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Remote; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ALICE; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_AUTHOR; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_COUNTER; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_GROUP_ID; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_INTRODUCEE_A; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_INTRODUCEE_B; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_INTRODUCER; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_LOCAL; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_MAC_KEY; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_MASTER_KEY; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_REMOTE; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ROLE; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_SESSION_ID; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_STATE; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; +import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES; + +@Immutable +@NotNullByDefault +class MailboxSessionParserImpl implements MailboxSessionParser { + + private final ClientHelper clientHelper; + + @Inject + MailboxSessionParserImpl(ClientHelper clientHelper) { + this.clientHelper = clientHelper; + } + + @Override + public BdfDictionary getSessionQuery(SessionId s) { + return BdfDictionary.of(new BdfEntry(SESSION_KEY_SESSION_ID, s)); + } + + @Override + public Role getRole(BdfDictionary d) throws FormatException { + return fromValue(d.getLong(SESSION_KEY_ROLE).intValue()); + } + + @Override + public long getMessageCounter(BdfDictionary d) throws FormatException { + return d.getLong(SESSION_KEY_COUNTER); + } + + @Override + public OwnerSession parseOwnerSession(BdfDictionary d) + throws FormatException { + if (getRole(d) != OWNER) throw new IllegalArgumentException(); + SessionId sessionId = getSessionId(d); + long sessionCounter = getSessionCounter(d); + OwnerState state = OwnerState.fromValue(getState(d)); + long requestTimestamp = d.getLong(SESSION_KEY_REQUEST_TIMESTAMP); + Introducee introduceeA = parseIntroducee(sessionId, + d.getDictionary(SESSION_KEY_INTRODUCEE_A)); + Introducee introduceeB = parseIntroducee(sessionId, + d.getDictionary(SESSION_KEY_INTRODUCEE_B)); + return new OwnerSession(sessionId, state, requestTimestamp, introduceeA, + introduceeB, sessionCounter); + } + + private Introducee parseIntroducee(SessionId sessionId, BdfDictionary d) + throws FormatException { + MessageId lastLocalMessageId = + getMessageId(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID); + MessageId lastRemoteMessageId = + getMessageId(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID); + long localTimestamp = d.getLong(SESSION_KEY_LOCAL_TIMESTAMP); + GroupId groupId = getGroupId(d, SESSION_KEY_GROUP_ID); + Author author = getAuthor(d, SESSION_KEY_AUTHOR); + return new Introducee(sessionId, groupId, author, localTimestamp, + lastLocalMessageId, lastRemoteMessageId); + } + + @Override + public MailboxSession parseMailboxSession(GroupId introducerGroupId, + BdfDictionary d) throws FormatException { + if (getRole(d) != MAILBOX) throw new IllegalArgumentException(); + SessionId sessionId = getSessionId(d); + long sessionCounter = getSessionCounter(d); + MailboxState state = MailboxState.fromValue(getState(d)); + long requestTimestamp = d.getLong(SESSION_KEY_REQUEST_TIMESTAMP); + Author introducer = getAuthor(d, SESSION_KEY_INTRODUCER); + 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<TransportId, KeySetId> transportKeys = parseTransportKeys( + d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS)); + return new MailboxSession(sessionId, state, requestTimestamp, + introducerGroupId, introducer, local, remote, masterKey, + transportKeys, sessionCounter); + } + + @Override + public IntroduceeSession parseIntroduceeSession(GroupId introducerGroupId, + BdfDictionary d) throws FormatException { + if (getRole(d) != INTRODUCEE) throw new IllegalArgumentException(); + SessionId sessionId = getSessionId(d); + long sessionCounter = getSessionCounter(d); + IntroduceeState state = IntroduceeState.fromValue(getState(d)); + long requestTimestamp = d.getLong(SESSION_KEY_REQUEST_TIMESTAMP); + Author introducer = getAuthor(d, SESSION_KEY_INTRODUCER); + 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<TransportId, KeySetId> transportKeys = parseTransportKeys( + d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS)); + return new IntroduceeSession(sessionId, state, requestTimestamp, + introducerGroupId, introducer, local, remote, masterKey, + transportKeys, sessionCounter); + } + + private Local parseLocal(BdfDictionary d) throws FormatException { + boolean alice = d.getBoolean(SESSION_KEY_ALICE); + MessageId lastLocalMessageId = + getMessageId(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID); + long localTimestamp = d.getLong(SESSION_KEY_LOCAL_TIMESTAMP); + byte[] ephemeralPublicKey = + d.getOptionalRaw(SESSION_KEY_EPHEMERAL_PUBLIC_KEY); + BdfDictionary tpDict = + d.getOptionalDictionary(SESSION_KEY_TRANSPORT_PROPERTIES); + byte[] ephemeralPrivateKey = + d.getOptionalRaw(SESSION_KEY_EPHEMERAL_PRIVATE_KEY); + Map<TransportId, TransportProperties> transportProperties = + tpDict == null ? null : clientHelper + .parseAndValidateTransportPropertiesMap(tpDict); + long acceptTimestamp = d.getLong(SESSION_KEY_ACCEPT_TIMESTAMP); + byte[] macKey = d.getOptionalRaw(SESSION_KEY_MAC_KEY); + return new Local(alice, lastLocalMessageId, localTimestamp, + ephemeralPublicKey, ephemeralPrivateKey, acceptTimestamp, + macKey); + } + + private Remote parseRemote(BdfDictionary d) throws FormatException { + boolean alice = d.getBoolean(SESSION_KEY_ALICE); + Author remoteAuthor = getAuthor(d, SESSION_KEY_REMOTE_AUTHOR); + MessageId lastRemoteMessageId = + getMessageId(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID); + byte[] ephemeralPublicKey = + d.getOptionalRaw(SESSION_KEY_EPHEMERAL_PUBLIC_KEY); + BdfDictionary tpDict = + d.getOptionalDictionary(SESSION_KEY_TRANSPORT_PROPERTIES); + Map<TransportId, TransportProperties> transportProperties = + tpDict == null ? null : clientHelper + .parseAndValidateTransportPropertiesMap(tpDict); + long acceptTimestamp = d.getLong(SESSION_KEY_ACCEPT_TIMESTAMP); + byte[] macKey = d.getOptionalRaw(SESSION_KEY_MAC_KEY); + return new Remote(alice, remoteAuthor, lastRemoteMessageId, + ephemeralPublicKey, transportProperties, acceptTimestamp, + macKey); + } + + private int getState(BdfDictionary d) throws FormatException { + return d.getLong(SESSION_KEY_STATE).intValue(); + } + + private long getSessionCounter(BdfDictionary d) throws FormatException { + return d.getLong(SESSION_KEY_COUNTER); + } + + private SessionId getSessionId(BdfDictionary d) throws FormatException { + byte[] b = d.getRaw(SESSION_KEY_SESSION_ID); + return new SessionId(b); + } + + @Nullable + private MessageId getMessageId(BdfDictionary d, String key) + throws FormatException { + byte[] b = d.getOptionalRaw(key); + return b == null ? null : new MessageId(b); + } + + private GroupId getGroupId(BdfDictionary d, String key) + throws FormatException { + return new GroupId(d.getRaw(key)); + } + + private Author getAuthor(BdfDictionary d, String key) + throws FormatException { + return clientHelper.parseAndValidateAuthor(d.getList(key)); + } + + @Nullable + private Map<TransportId, KeySetId> parseTransportKeys( + @Nullable BdfDictionary d) throws FormatException { + if (d == null) return null; + Map<TransportId, KeySetId> map = new HashMap<>(d.size()); + for (String key : d.keySet()) { + map.put(new TransportId(key), + new KeySetId(d.getLong(key).intValue())); + } + return map; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxState.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxState.java new file mode 100644 index 000000000..ecffd8110 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MailboxState.java @@ -0,0 +1,37 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +enum MailboxState implements State { + + START(0), + AWAIT_LOCAL_RESPONSE(1), + LOCAL_DECLINED(2), + LOCAL_ACCEPTED(3), + AWAIT_REMOTE_RESPONSE(4), + REMOTE_DECLINED(5), + REMOTE_ACCEPTED(6), + CONTACT_ADDED(7); + + private final int value; + + MailboxState(int value) { + this.value = value; + } + + @Override + public int getValue() { + return value; + } + + static MailboxState fromValue(int value) throws FormatException { + for (MailboxState s : values()) if (s.value == value) return s; + throw new FormatException(); + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MessageMetadata.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MessageMetadata.java new file mode 100644 index 000000000..9c0c50617 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MessageMetadata.java @@ -0,0 +1,55 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class MessageMetadata { + + private final MessageType type; + @Nullable + private final SessionId sessionId; + private final long timestamp, counter; + private final boolean local; + private final boolean available; + + MessageMetadata(MessageType type, @Nullable SessionId sessionId, + long timestamp, boolean local, long counter, boolean available) { + this.type = type; + this.sessionId = sessionId; + this.timestamp = timestamp; + this.local = local; + this.counter = counter; + this.available = available; + } + + MessageType getMessageType() { + return type; + } + + @Nullable + public SessionId getSessionId() { + return sessionId; + } + + long getTimestamp() { + return timestamp; + } + + boolean isLocal() { + return local; + } + + long getCounter() { + return counter; + } + + boolean isAvailableToAnswer() { + return available; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MessageType.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MessageType.java new file mode 100644 index 000000000..0b35f7721 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/MessageType.java @@ -0,0 +1,30 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +enum MessageType { + + MAILBOX_REQUEST(0), MAILBOX_ACCEPT(1), INTRODUCEE_REQUEST(2), + INTRODUCEE_ACCEPT(3), CONTACT_INFO(4), MAILBOX_AUTH(5), DECLINE(5), ABORT(6); + + private final int value; + + MessageType(int value) { + this.value = value; + } + + int getValue() { + return value; + } + + static MessageType fromValue(int value) throws FormatException { + for (MessageType m : values()) if (m.value == value) return m; + throw new FormatException(); + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java new file mode 100644 index 000000000..bc05091b6 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java @@ -0,0 +1,218 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.properties.TransportPropertyManager; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.transport.KeyManager; +import org.briarproject.briar.api.client.ProtocolStateException; +import org.briarproject.briar.api.mailbox.event.MailboxIntroductionResponseReceivedEvent; +import org.briarproject.briar.api.mailbox.event.MailboxIntroductionSucceededEvent; + +import javax.inject.Inject; + +import static org.briarproject.briar.mailbox.OwnerState.ADDED; +import static org.briarproject.briar.mailbox.OwnerState.AWAIT_AUTH_M; +import static org.briarproject.briar.mailbox.OwnerState.AWAIT_RESPONSE_B; +import static org.briarproject.briar.mailbox.OwnerState.AWAIT_RESPONSE_M; + +class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> { + + @Inject + OwnerProtocolEngine(DatabaseComponent db, ClientHelper clientHelper, + ContactManager contactManager, + ContactGroupFactory contactGroupFactory, + IdentityManager identityManager, + MailboxMessageParser messageParser, + MailboxMessageEncoder messageEncoder, Clock clock, + MailboxIntroductionCrypto crypto, KeyManager keyManager, + TransportPropertyManager transportPropertyManager) { + super(db, clientHelper, contactManager, contactGroupFactory, + identityManager, messageParser, messageEncoder, + clock, crypto, keyManager, transportPropertyManager); + } + + @Override + public OwnerSession onRequestAction(Transaction txn, OwnerSession s, + long timestamp) throws DbException { + switch (s.getState()) { + case START: + return onLocalRequest(txn, s, timestamp); + case AWAIT_RESPONSE_M: + case M_DECLINED: + case AWAIT_RESPONSE_B: + case B_DECLINED: + case AWAIT_AUTH_M: + case ADDED: + throw new ProtocolStateException(); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public OwnerSession onAcceptAction(Transaction txn, OwnerSession session, + long timestamp) throws DbException { + throw new UnsupportedOperationException(); + } + + @Override + public OwnerSession onDeclineAction(Transaction txn, OwnerSession session, + long timestamp) throws DbException { + throw new UnsupportedOperationException(); + } + + @Override + public OwnerSession onRequestMessage(Transaction txn, OwnerSession session, + RequestMessage m) throws DbException, FormatException { + // return abort(txn, session); + throw new UnsupportedOperationException(); + } + + @Override + public OwnerSession onMailboxAcceptMessage(Transaction txn, OwnerSession s, + MailboxAcceptMessage m) throws DbException, FormatException { + switch (s.getState()) { + case START: + case AWAIT_RESPONSE_M: + return onMailboxAccept(txn, s, m); + case M_DECLINED: + case AWAIT_RESPONSE_B: + case B_DECLINED: + case AWAIT_AUTH_M: + case ADDED: + throw new ProtocolStateException(); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public OwnerSession onIntroduceeRequestMessage(Transaction txn, + OwnerSession session, IntroduceeRequestMessage m) + throws DbException, FormatException { + throw new UnsupportedOperationException(); + } + + @Override + public OwnerSession onContactAcceptMessage(Transaction txn, + OwnerSession session, IntroduceeRequestMessage m) + throws DbException, FormatException { + throw new UnsupportedOperationException(); + } + + private OwnerSession onMailboxAccept(Transaction txn, OwnerSession s, + MailboxAcceptMessage m) throws DbException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s.getMailbox().lastRemoteMessageId, + m.getPreviousMessageId())) return abort(txn, s); + Message forward = sendIntroduceeRequestMessage(txn, s.getIntroducee(), + clock.currentTimeMillis(), s.getMailbox().author, + m.getEphemeralPublicKey(), + m.getAcceptTimestamp(), + s.getMessageCounter()); + broadcastMailboxIntroductionResponseReceived(txn, s.getMailbox().author, + s.getIntroducee().author); + return new OwnerSession(s.getSessionId(), AWAIT_RESPONSE_B, + s.getRequestTimestamp(), + new Introducee(s.getMailbox(), m.getMessageId()), + new Introducee(s.getIntroducee(), forward), + s.getMessageCounter()); + } + + @Override + public OwnerSession onDeclineMessage(Transaction txn, OwnerSession session, + DeclineMessage m) throws DbException, FormatException { + return null; + } + + @Override + public OwnerSession onAuthMessage(Transaction txn, OwnerSession s, + MailboxAuthMessage m) throws DbException, FormatException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s.getMailbox().getLastLocalMessageId(), + m.getPreviousMessageId())) return abort(txn, s); + Message forward = sendMailboxAuthMessage(txn, s.getIntroducee(), + clock.currentTimeMillis(), m.getTransportProperties(), + m.getMac(), m.getSignature(), s.getMessageCounter()); + Contact c = contactManager + .getContact(txn, s.getIntroducee().author.getId(), + identityManager.getLocalAuthor().getId()); + db.setMailboxForContact(txn, c.getId(), null, c.getId()); + txn.attach(new MailboxIntroductionSucceededEvent(c)); + return new OwnerSession(s.getSessionId(), ADDED, + s.getRequestTimestamp(), + new Introducee(s.getMailbox(), m.getMessageId()), + new Introducee(s.getIntroducee(), forward), + s.getMessageCounter()); + } + + @Override + public OwnerSession onAbortMessage(Transaction txn, OwnerSession session, + AbortMessage m) throws DbException, FormatException { + return null; + } + + @Override + public OwnerSession onIntroduceeAcceptMessage(Transaction txn, + OwnerSession s, IntroduceeAcceptMessage m) throws DbException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s.getIntroducee().getLastLocalMessageId(), + m.getPreviousMessageId())) return abort(txn, s); + Message forward = sendIntroduceeResponseMessage(txn, s.getMailbox(), + s.getMailbox().lastRemoteMessageId, + clock.currentTimeMillis(), + m.getEphemeralPublicKey(), + m.getMac(), m.getSignature(), + m.getAcceptTimestamp(), + s.getMessageCounter()); + return new OwnerSession(s.getSessionId(), AWAIT_AUTH_M, + s.getRequestTimestamp(), + new Introducee(s.getMailbox(), forward), + new Introducee(s.getIntroducee(), m.getMessageId()), + s.getMessageCounter()); + } + + private OwnerSession onLocalRequest(Transaction txn, OwnerSession s, + long timestamp) throws DbException { + // Send REQUEST messages + long maxIntroduceeTimestamp = + Math.max(getLocalTimestamp(s, s.getMailbox()), + getLocalTimestamp(s, s.getIntroducee())); + long localTimestamp = Math.max(timestamp, maxIntroduceeTimestamp); + Message sentMailbox = + sendMailboxRequestMessage(txn, s.getMailbox(), localTimestamp, + s.getIntroducee().author, s.getMessageCounter()); + // Move to the AWAIT_RESPONSES state + Introducee mailbox = new Introducee(s.getMailbox(), sentMailbox); + Introducee b = new Introducee(s.getIntroducee().sessionId, + s.getIntroducee().groupId, s.getIntroducee().author); + return new OwnerSession(s.getSessionId(), AWAIT_RESPONSE_M, + localTimestamp, mailbox, b, s.getMessageCounter()); + } + + private long getLocalTimestamp(OwnerSession s, PeerSession p) { + return getLocalTimestamp(p.getLocalTimestamp(), + s.getRequestTimestamp()); + } + + private OwnerSession abort(Transaction txn, Session s) { + return null; + } + + void broadcastMailboxIntroductionResponseReceived(Transaction txn, + Author from, Author to) { + MailboxIntroductionResponseReceivedEvent e = + new MailboxIntroductionResponseReceivedEvent(from, to); + txn.attach(e); + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java new file mode 100644 index 000000000..d07b5ac22 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerSession.java @@ -0,0 +1,52 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.mailbox.Role; + +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.briar.api.mailbox.Role.OWNER; + +@Immutable +@NotNullByDefault +class OwnerSession extends Session<OwnerState> { + + private final Introducee mailbox, introducee; + + OwnerSession(SessionId sessionId, OwnerState state, + long requestTimestamp, Introducee mailbox, + Introducee introducee, long sessionCounter) { + super(sessionId, state, requestTimestamp, sessionCounter); + this.mailbox = mailbox; + this.introducee = introducee; + } + + OwnerSession(SessionId sessionId, GroupId groupIdA, Author authorA, + GroupId groupIdB, Author authorB, long sessionCounter) { + this(sessionId, OwnerState.START, -1, + new Introducee(sessionId, groupIdA, authorA), + new Introducee(sessionId, groupIdB, authorB), sessionCounter); + } + + public static OwnerSession finished(OwnerSession s) { + return null; + } + + @Override + Role getRole() { + return OWNER; + } + + Introducee getMailbox() { + return mailbox; + } + + Introducee getIntroducee() { + return introducee; + } + + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerState.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerState.java new file mode 100644 index 000000000..ba0531f2e --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/OwnerState.java @@ -0,0 +1,36 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +enum OwnerState implements State { + + START(0), + AWAIT_RESPONSE_M(1), + M_DECLINED(2), + AWAIT_RESPONSE_B(3), + B_DECLINED(4), + AWAIT_AUTH_M(5), + ADDED(6); + + private final int value; + + OwnerState(int value) { + this.value = value; + } + + @Override + public int getValue() { + return value; + } + + static OwnerState fromValue(int value) throws FormatException { + for (OwnerState s : values()) if (s.value == value) return s; + throw new FormatException(); + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/PeerSession.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/PeerSession.java new file mode 100644 index 000000000..69b944943 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/PeerSession.java @@ -0,0 +1,25 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.Nullable; + +@NotNullByDefault +interface PeerSession { + + SessionId getSessionId(); + + GroupId getContactGroupId(); + + long getLocalTimestamp(); + + @Nullable + MessageId getLastLocalMessageId(); + + @Nullable + MessageId getLastRemoteMessageId(); + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java new file mode 100644 index 000000000..2ab36fcc1 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java @@ -0,0 +1,46 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@NotNullByDefault +interface ProtocolEngine<S extends Session> { + + S onRequestAction(Transaction txn, S session, long timestamp) + throws DbException; + + S onAcceptAction(Transaction txn, S session, long timestamp) + throws DbException; + + S onDeclineAction(Transaction txn, S session, long timestamp) + throws DbException; + + S onRequestMessage(Transaction txn, S session, RequestMessage m) + throws DbException, FormatException; + + S onMailboxAcceptMessage(Transaction txn, S session, + MailboxAcceptMessage m) + throws DbException, FormatException; + + S onIntroduceeRequestMessage(Transaction txn, + S session, IntroduceeRequestMessage m) + throws DbException, FormatException; + + S onContactAcceptMessage(Transaction txn, S session, + IntroduceeRequestMessage m) + throws DbException, FormatException; + + S onDeclineMessage(Transaction txn, S session, DeclineMessage m) + throws DbException, FormatException; + + S onAuthMessage(Transaction txn, S session, MailboxAuthMessage m) + throws DbException, FormatException; + + S onAbortMessage(Transaction txn, S session, AbortMessage m) + throws DbException, FormatException; + + S onIntroduceeAcceptMessage(Transaction txn, S session, + IntroduceeAcceptMessage acceptMessage) throws DbException; +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/RequestMessage.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/RequestMessage.java new file mode 100644 index 000000000..2e25c9094 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/RequestMessage.java @@ -0,0 +1,28 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class RequestMessage extends AbstractMailboxIntroductionMessage { + + + private final Author author; + + protected RequestMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + Author author) { + super(messageId, groupId, timestamp, previousMessageId); + this.author = author; + } + + public Author getAuthor() { + return author; + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/Session.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/Session.java new file mode 100644 index 000000000..ab65947ed --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/Session.java @@ -0,0 +1,44 @@ +package org.briarproject.briar.mailbox; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.mailbox.Role; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +abstract class Session<S extends State> { + + private final SessionId sessionId; + private final S state; + private final long requestTimestamp; + private long messageCounter; + + Session(SessionId sessionId, S state, long requestTimestamp, + long messageCounter) { + this.sessionId = sessionId; + this.state = state; + this.requestTimestamp = requestTimestamp; + this.messageCounter = messageCounter; + } + + abstract Role getRole(); + + public SessionId getSessionId() { + return sessionId; + } + + S getState() { + return state; + } + + long getRequestTimestamp() { + return requestTimestamp; + } + + synchronized long getMessageCounter() { + return messageCounter; + } + +} diff --git a/mailbox-core/src/main/java/org/briarproject/briar/mailbox/State.java b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/State.java new file mode 100644 index 000000000..66a2e80d1 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/briar/mailbox/State.java @@ -0,0 +1,7 @@ +package org.briarproject.briar.mailbox; + +interface State { + + int getValue(); + +} diff --git a/mailbox-core/witness.gradle b/mailbox-core/witness.gradle new file mode 100644 index 000000000..ace73b9df --- /dev/null +++ b/mailbox-core/witness.gradle @@ -0,0 +1,4 @@ +dependencyVerification { + verify = [ + ] +} diff --git a/settings.gradle b/settings.gradle index 1f8110466..1c273d335 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,3 +6,4 @@ include ':briar-api' include ':briar-core' include ':briar-android' include ':mailbox-android' +include ':mailbox-core' -- GitLab