From 218b2f7ff9cbef129e79a75c117c9587ce36aed1 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 24 Apr 2018 10:03:27 -0300 Subject: [PATCH 01/21] Fix activating transport keys in JdbcDatabase --- .../src/main/java/org/briarproject/bramble/db/JdbcDatabase.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index dd5874f6e..f63bcb0a6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -2898,6 +2898,8 @@ abstract class JdbcDatabase implements Database { String sql = "UPDATE outgoingKeys SET active = true" + " WHERE transportId = ? AND keySetId = ?"; ps = txn.prepareStatement(sql); + ps.setString(1, t.getString()); + ps.setInt(2, k.getInt()); int affected = ps.executeUpdate(); if (affected < 0 || affected > 1) throw new DbStateException(); ps.close(); From 155c6a5613e4c23ad012b2d77630f1f9b5e4851c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 16 Apr 2018 16:00:17 -0300 Subject: [PATCH 02/21] Messages and Validator for new Introduction Client --- .../bramble/api/crypto/CryptoConstants.java | 6 + .../bramble/test/ValidatorTestCase.java | 32 +- .../introduction2/IntroductionConstants.java | 13 + .../briar/introduction2/AbortMessage.java | 27 ++ .../briar/introduction2/AcceptMessage.java | 46 ++ .../briar/introduction2/ActivateMessage.java | 26 ++ .../briar/introduction2/AuthMessage.java | 38 ++ .../briar/introduction2/DeclineMessage.java | 28 ++ .../introduction2/IntroductionMessage.java | 45 ++ .../introduction2/IntroductionModule.java | 39 ++ .../introduction2/IntroductionValidator.java | 167 +++++++ .../briar/introduction2/MessageEncoder.java | 15 + .../briar/introduction2/MessageType.java | 29 ++ .../briar/introduction2/RequestMessage.java | 36 ++ .../IntroductionValidatorTest.java | 426 ++++++++++++++++++ .../GroupMessageValidatorTest.java | 8 - .../briar/sharing/SharingValidatorTest.java | 2 +- 17 files changed, 966 insertions(+), 17 deletions(-) create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/AbortMessage.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/ActivateMessage.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/AuthMessage.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/DeclineMessage.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionMessage.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionModule.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/MessageType.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/RequestMessage.java create mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java index 3b12c5cf7..b9f772d2b 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java @@ -16,4 +16,10 @@ public interface CryptoConstants { * The maximum length of a signature in bytes. */ int MAX_SIGNATURE_BYTES = 64; + + /** + * The length of a MAC in bytes. + */ + int MAC_BYTES = SecretKey.LENGTH; + } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java b/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java index 799722344..66a4a3b56 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java @@ -1,17 +1,20 @@ package org.briarproject.bramble.test; import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.identity.Author; 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.jmock.Expectations; +import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getGroup; -import static org.briarproject.bramble.test.TestUtils.getRandomBytes; -import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.bramble.test.TestUtils.getMessage; public abstract class ValidatorTestCase extends BrambleMockTestCase { @@ -24,10 +27,23 @@ public abstract class ValidatorTestCase extends BrambleMockTestCase { protected final Group group = getGroup(getClientId()); protected final GroupId groupId = group.getId(); protected final byte[] descriptor = group.getDescriptor(); - protected final MessageId messageId = new MessageId(getRandomId()); - protected final long timestamp = 1234567890 * 1000L; - protected final byte[] raw = getRandomBytes(123); - protected final Message message = - new Message(messageId, groupId, timestamp, raw); + protected final Message message = getMessage(groupId); + protected final MessageId messageId = message.getId(); + protected final long timestamp = message.getTimestamp(); + protected final byte[] raw = message.getRaw(); + protected final Author author = getAuthor(); + protected final BdfList authorList = BdfList.of( + author.getFormatVersion(), + author.getName(), + author.getPublicKey() + ); -} + protected void expectParseAuthor(BdfList authorList, Author author) + throws Exception { + context.checking(new Expectations() {{ + oneOf(clientHelper).parseAndValidateAuthor(authorList); + will(returnValue(author)); + }}); + } + +} \ No newline at end of file diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java new file mode 100644 index 000000000..6b91b33c0 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java @@ -0,0 +1,13 @@ +package org.briarproject.briar.api.introduction2; + +import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; + +public interface IntroductionConstants { + + /** + * The maximum length of the introducer's optional message to the + * introducees in UTF-8 bytes. + */ + int MAX_REQUEST_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/AbortMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/AbortMessage.java new file mode 100644 index 000000000..9d7112407 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/AbortMessage.java @@ -0,0 +1,27 @@ +package org.briarproject.briar.introduction2; + +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 IntroductionMessage { + + 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/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java new file mode 100644 index 000000000..b9bf92b6c --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java @@ -0,0 +1,46 @@ +package org.briarproject.briar.introduction2; + +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 AcceptMessage extends IntroductionMessage { + + private final SessionId sessionId; + private final byte[] ephemeralPublicKey; + private final Map transportProperties; + + protected AcceptMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, + byte[] ephemeralPublicKey, + Map transportProperties) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + this.ephemeralPublicKey = ephemeralPublicKey; + this.transportProperties = transportProperties; + } + + public SessionId getSessionId() { + return sessionId; + } + + public byte[] getEphemeralPublicKey() { + return ephemeralPublicKey; + } + + public Map getTransportProperties() { + return transportProperties; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/ActivateMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/ActivateMessage.java new file mode 100644 index 000000000..0d5e935b4 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/ActivateMessage.java @@ -0,0 +1,26 @@ +package org.briarproject.briar.introduction2; + +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.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class ActivateMessage extends IntroductionMessage { + + private final SessionId sessionId; + + protected ActivateMessage(MessageId messageId, GroupId groupId, + long timestamp, MessageId previousMessageId, SessionId sessionId) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + } + + public SessionId getSessionId() { + return sessionId; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/AuthMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/AuthMessage.java new file mode 100644 index 000000000..5833a64d6 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/AuthMessage.java @@ -0,0 +1,38 @@ +package org.briarproject.briar.introduction2; + +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.concurrent.Immutable; + +@Immutable +@NotNullByDefault +class AuthMessage extends IntroductionMessage { + + private final SessionId sessionId; + private final byte[] mac, signature; + + protected AuthMessage(MessageId messageId, GroupId groupId, + long timestamp, MessageId previousMessageId, SessionId sessionId, + byte[] mac, byte[] signature) { + super(messageId, groupId, timestamp, previousMessageId); + this.sessionId = sessionId; + this.mac = mac; + this.signature = signature; + } + + public SessionId getSessionId() { + return sessionId; + } + + public byte[] getMac() { + return mac; + } + + public byte[] getSignature() { + return signature; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/DeclineMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/DeclineMessage.java new file mode 100644 index 000000000..c419704c5 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/DeclineMessage.java @@ -0,0 +1,28 @@ +package org.briarproject.briar.introduction2; + +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 IntroductionMessage { + + 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/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionMessage.java new file mode 100644 index 000000000..24a252f5e --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionMessage.java @@ -0,0 +1,45 @@ +package org.briarproject.briar.introduction2; + +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 IntroductionMessage { + + private final MessageId messageId; + private final GroupId groupId; + private final long timestamp; + @Nullable + private final MessageId previousMessageId; + + IntroductionMessage(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/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionModule.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionModule.java new file mode 100644 index 000000000..1a64538ea --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionModule.java @@ -0,0 +1,39 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.sync.ValidationManager; +import org.briarproject.bramble.api.system.Clock; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; + +@Module +public class IntroductionModule { + + public static class EagerSingletons { + @Inject + IntroductionValidator introductionValidator; + } + + @Provides + @Singleton + IntroductionValidator provideValidator(ValidationManager validationManager, + MessageEncoder messageEncoder, MetadataEncoder metadataEncoder, + ClientHelper clientHelper, Clock clock) { + + IntroductionValidator introductionValidator = + new IntroductionValidator(messageEncoder, clientHelper, + metadataEncoder, clock); + validationManager.registerMessageValidator(CLIENT_ID, + introductionValidator); + + return introductionValidator; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java new file mode 100644 index 000000000..ce8359701 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java @@ -0,0 +1,167 @@ +package org.briarproject.briar.introduction2; + +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.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; +import static org.briarproject.bramble.util.ValidationUtils.checkLength; +import static org.briarproject.bramble.util.ValidationUtils.checkSize; +import static org.briarproject.briar.api.introduction2.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; +import static org.briarproject.briar.introduction2.MessageType.ACCEPT; +import static org.briarproject.briar.introduction2.MessageType.AUTH; +import static org.briarproject.briar.introduction2.MessageType.REQUEST; + + +@Immutable +@NotNullByDefault +class IntroductionValidator extends BdfMessageValidator { + + private final MessageEncoder messageEncoder; + + IntroductionValidator(MessageEncoder 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 REQUEST: + return validateRequestMessage(m, body); + case ACCEPT: + return validateAcceptMessage(m, body); + case AUTH: + return validateAuthMessage(m, body); + case DECLINE: + case ACTIVATE: + 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); + + String msg = body.getOptionalString(3); + checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH); + + BdfDictionary meta = messageEncoder + .encodeRequestMetadata(REQUEST, m.getTimestamp(), false, + false, false, false, false); + 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, 5); + + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); + + byte[] previousMessageId = body.getRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + byte[] ephemeralPublicKey = body.getRaw(3); + checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH); + + BdfDictionary transportProperties = body.getDictionary(4); + if (transportProperties.size() < 1) throw new FormatException(); + for (String tId : transportProperties.keySet()) { + checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH); + BdfDictionary tProps = transportProperties.getDictionary(tId); + clientHelper.parseAndValidateTransportProperties(tProps); + } + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(ACCEPT, sessionId, m.getTimestamp(), false, + false, false); + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + + private BdfMessageContext validateAuthMessage(Message m, BdfList body) + throws FormatException { + checkSize(body, 5); + + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); + + byte[] previousMessageId = body.getRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + byte[] mac = body.getRaw(3); + checkLength(mac, MAC_BYTES); + + byte[] signature = body.getRaw(4); + checkLength(signature, 1, MAX_SIGNATURE_BYTES); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(AUTH, sessionId, m.getTimestamp(), false, false, + false); + 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.getRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(type, sessionId, m.getTimestamp(), false, false, + false); + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java new file mode 100644 index 000000000..25371b68c --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java @@ -0,0 +1,15 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.briar.api.client.SessionId; + +interface MessageEncoder { + + BdfDictionary encodeRequestMetadata(MessageType type, + long timestamp, boolean local, boolean read, boolean visible, + boolean available, boolean accepted); + + BdfDictionary encodeMetadata(MessageType type, SessionId sessionId, + long timestamp, boolean local, boolean read, boolean visible); + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageType.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageType.java new file mode 100644 index 000000000..bb34b7da3 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageType.java @@ -0,0 +1,29 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +enum MessageType { + + REQUEST(0), ACCEPT(1), DECLINE(2), AUTH(3), ACTIVATE(4), ABORT(5); + + 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/briar-core/src/main/java/org/briarproject/briar/introduction2/RequestMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/RequestMessage.java new file mode 100644 index 000000000..470fb3966 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/RequestMessage.java @@ -0,0 +1,36 @@ +package org.briarproject.briar.introduction2; + +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 IntroductionMessage { + + private final Author author; + @Nullable + private final String message; + + protected RequestMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + Author author, @Nullable String message) { + super(messageId, groupId, timestamp, previousMessageId); + this.author = author; + this.message = message; + } + + public Author getAuthor() { + return author; + } + + @Nullable + public String getMessage() { + return message; + } + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java new file mode 100644 index 000000000..fc0109497 --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java @@ -0,0 +1,426 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.BdfMessageContext; +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.sync.MessageId; +import org.briarproject.bramble.test.ValidatorTestCase; +import org.briarproject.briar.api.client.SessionId; +import org.jmock.Expectations; +import org.junit.Test; + +import javax.annotation.Nullable; + +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.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.bramble.util.StringUtils.getRandomString; +import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; +import static org.briarproject.briar.introduction2.MessageType.ABORT; +import static org.briarproject.briar.introduction2.MessageType.ACCEPT; +import static org.briarproject.briar.introduction2.MessageType.ACTIVATE; +import static org.briarproject.briar.introduction2.MessageType.AUTH; +import static org.briarproject.briar.introduction2.MessageType.DECLINE; +import static org.briarproject.briar.introduction2.MessageType.REQUEST; +import static org.junit.Assert.assertEquals; + +public class IntroductionValidatorTest extends ValidatorTestCase { + + private final MessageEncoder messageEncoder = + context.mock(MessageEncoder.class); + private final IntroductionValidator validator = + new IntroductionValidator(messageEncoder, clientHelper, + metadataEncoder, clock); + + private final SessionId sessionId = new SessionId(getRandomId()); + private final MessageId previousMsgId = new MessageId(getRandomId()); + private final String text = + getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH); + private final BdfDictionary meta = new BdfDictionary(); + private final BdfDictionary transportProperties = BdfDictionary.of( + new BdfEntry("transportId", new BdfDictionary()) + ); + private final byte[] mac = getRandomBytes(MAC_BYTES); + private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES); + + // + // Introduction REQUEST + // + + @Test + public void testAcceptsRequest() throws Exception { + BdfList body = BdfList.of(REQUEST.getValue(), previousMsgId.getBytes(), + authorList, text); + + expectParseAuthor(authorList, author); + expectEncodeRequestMetadata(); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test + public void testAcceptsRequestWithPreviousMsgIdNull() throws Exception { + BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, text); + + expectParseAuthor(authorList, author); + expectEncodeRequestMetadata(); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, null); + } + + @Test + public void testAcceptsRequestWithMessageNull() throws Exception { + BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, null); + + expectParseAuthor(authorList, author); + expectEncodeRequestMetadata(); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, null); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForRequest() throws Exception { + BdfList body = BdfList.of(REQUEST.getValue(), null, authorList); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForRequest() throws Exception { + BdfList body = + BdfList.of(REQUEST.getValue(), null, authorList, text, null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsRawMessageForRequest() throws Exception { + BdfList body = + BdfList.of(REQUEST.getValue(), null, authorList, getRandomId()); + expectParseAuthor(authorList, author); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsStringMessageIdForRequest() throws Exception { + BdfList body = + BdfList.of(REQUEST.getValue(), "NoMessageId", authorList, null); + validator.validateMessage(message, group, body); + } + + // + // Introduction ACCEPT + // + + @Test + public void testAcceptsAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + transportProperties); + context.checking(new Expectations() {{ + oneOf(clientHelper).parseAndValidateTransportProperties( + transportProperties.getDictionary("transportId")); + }}); + expectEncodeMetadata(ACCEPT); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + transportProperties, null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidSessionIdForAccept() throws Exception { + BdfList body = + BdfList.of(ACCEPT.getValue(), null, previousMsgId.getBytes(), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + transportProperties); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidPreviousMsgIdForAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + null, getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + transportProperties); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongPublicKeyForAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), transportProperties); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsEmptyTransportPropertiesForAccept() + throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), new BdfDictionary()); + validator.validateMessage(message, group, body); + } + + // + // Introduction DECLINE + // + + @Test + public void testAcceptsDecline() throws Exception { + BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), + previousMsgId.getBytes()); + + expectEncodeMetadata(DECLINE); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForDecline() throws Exception { + BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForDecline() throws Exception { + BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidSessionIdForDecline() throws Exception { + BdfList body = + BdfList.of(DECLINE.getValue(), null, previousMsgId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidPreviousMsgIdForDecline() throws Exception { + BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), + null); + validator.validateMessage(message, group, body); + } + + // + // Introduction AUTH + // + + @Test + public void testAcceptsAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac, signature); + + expectEncodeMetadata(AUTH); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac, signature, null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortMacForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), getRandomBytes(MAC_BYTES - 1), + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongMacForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), + getRandomBytes(MAC_BYTES + 1), signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidMacForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), null, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortSignatureForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac, getRandomBytes(0)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongSignatureForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac, + getRandomBytes(MAX_SIGNATURE_BYTES + 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidSignatureForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac, null); + validator.validateMessage(message, group, body); + } + + // + // Introduction ACTIVATE + // + + @Test + public void testAcceptsActivate() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), + previousMsgId.getBytes()); + + expectEncodeMetadata(ACTIVATE); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForActivate() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForActivate() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidSessionIdForActivate() throws Exception { + BdfList body = + BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidPreviousMsgIdForActivate() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), + null); + validator.validateMessage(message, group, body); + } + + // + // Introduction ABORT + // + + @Test + public void testAcceptsAbort() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes()); + + expectEncodeMetadata(ABORT); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForAbort() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForAbort() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidSessionIdForAbort() throws Exception { + BdfList body = + BdfList.of(ABORT.getValue(), null, previousMsgId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidPreviousMsgIdForAbort() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), + null); + validator.validateMessage(message, group, body); + } + + // + // Introduction Helper Methods + // + + private void expectEncodeRequestMetadata() { + context.checking(new Expectations() {{ + oneOf(messageEncoder) + .encodeRequestMetadata(REQUEST, message.getTimestamp(), + false, false, + false, false, false); + will(returnValue(meta)); + }}); + } + + private void expectEncodeMetadata(MessageType type) { + context.checking(new Expectations() {{ + oneOf(messageEncoder) + .encodeMetadata(type, sessionId, message.getTimestamp(), + false, false, false); + will(returnValue(meta)); + }}); + } + + private void assertExpectedContext(BdfMessageContext c, + @Nullable MessageId dependency) { + assertEquals(meta, c.getDictionary()); + if (dependency == null) { + assertEquals(0, c.getDependencies().size()); + } else { + assertEquals(dependency, c.getDependencies().iterator().next()); + } + } + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/GroupMessageValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/GroupMessageValidatorTest.java index 196ce09ca..3148da439 100644 --- a/briar-core/src/test/java/org/briarproject/briar/privategroup/GroupMessageValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/GroupMessageValidatorTest.java @@ -368,14 +368,6 @@ public class GroupMessageValidatorTest extends ValidatorTestCase { .getBoolean(KEY_INITIAL_JOIN_MSG)); } - private void expectParseAuthor(BdfList authorList, Author author) - throws Exception { - context.checking(new Expectations() {{ - oneOf(clientHelper).parseAndValidateAuthor(authorList); - will(returnValue(author)); - }}); - } - private void expectRejectAuthor(BdfList authorList) throws Exception { context.checking(new Expectations() {{ oneOf(clientHelper).parseAndValidateAuthor(authorList); diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java index 157c4ce79..f001fb680 100644 --- a/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java @@ -150,7 +150,7 @@ public abstract class SharingValidatorTest extends ValidatorTestCase { } void assertExpectedContext(BdfMessageContext messageContext, - @Nullable MessageId previousMsgId) throws FormatException { + @Nullable MessageId previousMsgId) { Collection dependencies = messageContext.getDependencies(); if (previousMsgId == null) { assertTrue(dependencies.isEmpty()); From 672a52b2e5014a4fc76adcb8035d9effbb316536 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 17 Apr 2018 18:13:35 -0300 Subject: [PATCH 03/21] Implement MessageEncoder and MessageParser --- .../briar/introduction2/AcceptMessage.java | 7 + .../introduction2/IntroductionConstants.java | 14 ++ .../introduction2/IntroductionValidator.java | 11 +- .../briar/introduction2/MessageEncoder.java | 52 +++- .../introduction2/MessageEncoderImpl.java | 205 ++++++++++++++++ .../briar/introduction2/MessageMetadata.java | 65 +++++ .../briar/introduction2/MessageParser.java | 37 +++ .../introduction2/MessageParserImpl.java | 144 +++++++++++ .../IntroductionValidatorTest.java | 22 +- .../MessageEncoderParserIntegrationTest.java | 231 ++++++++++++++++++ .../introduction2/MessageEncoderTest.java | 63 +++++ .../test/BriarIntegrationTestComponent.java | 3 + .../briar/test/BriarTestUtils.java | 10 + 13 files changed, 844 insertions(+), 20 deletions(-) create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java create mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java create mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java index b9bf92b6c..77d09c74a 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java @@ -18,16 +18,19 @@ class AcceptMessage extends IntroductionMessage { private final SessionId sessionId; private final byte[] ephemeralPublicKey; + private final long acceptTimestamp; private final Map transportProperties; protected AcceptMessage(MessageId messageId, GroupId groupId, long timestamp, @Nullable MessageId previousMessageId, SessionId sessionId, byte[] ephemeralPublicKey, + long acceptTimestamp, Map transportProperties) { super(messageId, groupId, timestamp, previousMessageId); this.sessionId = sessionId; this.ephemeralPublicKey = ephemeralPublicKey; + this.acceptTimestamp = acceptTimestamp; this.transportProperties = transportProperties; } @@ -39,6 +42,10 @@ class AcceptMessage extends IntroductionMessage { return ephemeralPublicKey; } + public long getAcceptTimestamp() { + return acceptTimestamp; + } + public Map getTransportProperties() { return transportProperties; } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java new file mode 100644 index 000000000..4098b2421 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java @@ -0,0 +1,14 @@ +package org.briarproject.briar.introduction2; + +interface IntroductionConstants { + + // 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_VISIBLE_IN_UI = "visibleInUi"; + String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer"; + String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted"; + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java index ce8359701..1bdf07ff6 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java @@ -28,7 +28,6 @@ import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.briar.api.introduction2.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; import static org.briarproject.briar.introduction2.MessageType.ACCEPT; import static org.briarproject.briar.introduction2.MessageType.AUTH; -import static org.briarproject.briar.introduction2.MessageType.REQUEST; @Immutable @@ -79,8 +78,8 @@ class IntroductionValidator extends BdfMessageValidator { checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH); BdfDictionary meta = messageEncoder - .encodeRequestMetadata(REQUEST, m.getTimestamp(), false, - false, false, false, false); + .encodeRequestMetadata(m.getTimestamp(), false, false, + false, false); if (previousMessageId == null) { return new BdfMessageContext(meta); } else { @@ -92,7 +91,7 @@ class IntroductionValidator extends BdfMessageValidator { private BdfMessageContext validateAcceptMessage(Message m, BdfList body) throws FormatException { - checkSize(body, 5); + checkSize(body, 6); byte[] sessionIdBytes = body.getRaw(1); checkLength(sessionIdBytes, UniqueId.LENGTH); @@ -103,7 +102,9 @@ class IntroductionValidator extends BdfMessageValidator { byte[] ephemeralPublicKey = body.getRaw(3); checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH); - BdfDictionary transportProperties = body.getDictionary(4); + body.getLong(4); + + BdfDictionary transportProperties = body.getDictionary(5); if (transportProperties.size() < 1) throw new FormatException(); for (String tId : transportProperties.keySet()) { checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java index 25371b68c..390ea9b0f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java @@ -1,15 +1,57 @@ package org.briarproject.briar.introduction2; 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 MessageEncoder { - BdfDictionary encodeRequestMetadata(MessageType type, - long timestamp, boolean local, boolean read, boolean visible, - boolean available, boolean accepted); + BdfDictionary encodeRequestMetadata(long timestamp, boolean local, + boolean read, boolean available, boolean accepted); - BdfDictionary encodeMetadata(MessageType type, SessionId sessionId, - long timestamp, boolean local, boolean read, boolean visible); + BdfDictionary encodeMetadata(MessageType type, + @Nullable SessionId sessionId, long timestamp, boolean local, + boolean read, boolean visible); + + void addSessionId(BdfDictionary meta, SessionId sessionId); + + void setVisibleInUi(BdfDictionary meta, boolean visible); + + void setAvailableToAnswer(BdfDictionary meta, boolean available); + + void setInvitationAccepted(BdfDictionary meta, boolean accepted); + + Message encodeRequestMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, Author author, + @Nullable String message); + + Message encodeAcceptMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId, + byte[] ephemeralPublicKey, long acceptTimestamp, + Map transportProperties); + + Message encodeDeclineMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId); + + Message encodeAuthMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId, + byte[] mac, byte[] signature); + + Message encodeActivateMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId); + + Message encodeAbortMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId); } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java new file mode 100644 index 000000000..7f44671e0 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java @@ -0,0 +1,205 @@ +package org.briarproject.briar.introduction2; + +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.client.MessageTrackerConstants.MSG_KEY_READ; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI; +import static org.briarproject.briar.introduction2.MessageType.ABORT; +import static org.briarproject.briar.introduction2.MessageType.ACCEPT; +import static org.briarproject.briar.introduction2.MessageType.ACTIVATE; +import static org.briarproject.briar.introduction2.MessageType.AUTH; +import static org.briarproject.briar.introduction2.MessageType.DECLINE; +import static org.briarproject.briar.introduction2.MessageType.REQUEST; + +@NotNullByDefault +class MessageEncoderImpl implements MessageEncoder { + + private final ClientHelper clientHelper; + private final MessageFactory messageFactory; + + @Inject + MessageEncoderImpl(ClientHelper clientHelper, + MessageFactory messageFactory) { + this.clientHelper = clientHelper; + this.messageFactory = messageFactory; + } + + @Override + public BdfDictionary encodeRequestMetadata(long timestamp, + boolean local, boolean read, boolean available, + boolean accepted) { + BdfDictionary meta = + encodeMetadata(REQUEST, null, timestamp, local, read, false); + meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available); + meta.put(MSG_KEY_INVITATION_ACCEPTED, accepted); + return meta; + } + + @Override + public BdfDictionary encodeMetadata(MessageType type, + @Nullable SessionId sessionId, long timestamp, boolean local, + boolean read, boolean visible) { + 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 != REQUEST) + throw new IllegalArgumentException(); + meta.put(MSG_KEY_TIMESTAMP, timestamp); + meta.put(MSG_KEY_LOCAL, local); + meta.put(MSG_KEY_READ, read); + meta.put(MSG_KEY_VISIBLE_IN_UI, visible); + return meta; + } + + @Override + public void addSessionId(BdfDictionary meta, SessionId sessionId) { + meta.put(MSG_KEY_SESSION_ID, sessionId); + } + + @Override + public void setVisibleInUi(BdfDictionary meta, boolean visible) { + meta.put(MSG_KEY_VISIBLE_IN_UI, visible); + } + + @Override + public void setAvailableToAnswer(BdfDictionary meta, boolean available) { + meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available); + } + + @Override + public void setInvitationAccepted(BdfDictionary meta, boolean accepted) { + meta.put(MSG_KEY_INVITATION_ACCEPTED, accepted); + } + + @Override + public Message encodeRequestMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, Author author, + @Nullable String message) { + if (message != null && message.equals("")) { + throw new IllegalArgumentException(); + } + BdfList body = BdfList.of( + REQUEST.getValue(), + previousMessageId, + clientHelper.toList(author), + message + ); + try { + return messageFactory.createMessage(contactGroupId, timestamp, + clientHelper.toByteArray(body)); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + @Override + public Message encodeAcceptMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId, + byte[] ephemeralPublicKey, long acceptTimestamp, + Map transportProperties) { + BdfList body = BdfList.of( + ACCEPT.getValue(), + sessionId, + previousMessageId, + ephemeralPublicKey, + acceptTimestamp, + encodeTransportProperties(transportProperties) + ); + try { + return messageFactory.createMessage(contactGroupId, timestamp, + clientHelper.toByteArray(body)); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + @Override + public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId) { + return encodeMessage(DECLINE, contactGroupId, sessionId, timestamp, + previousMessageId); + } + + @Override + public Message encodeAuthMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId, + byte[] mac, byte[] signature) { + BdfList body = BdfList.of( + AUTH.getValue(), + sessionId, + previousMessageId, + mac, + signature + ); + try { + return messageFactory.createMessage(contactGroupId, timestamp, + clientHelper.toByteArray(body)); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + @Override + public Message encodeActivateMessage(GroupId contactGroupId, long timestamp, + @Nullable MessageId previousMessageId, SessionId sessionId) { + return encodeMessage(ACTIVATE, 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 + ); + try { + return messageFactory.createMessage(contactGroupId, timestamp, + clientHelper.toByteArray(body)); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + private BdfDictionary encodeTransportProperties( + Map map) { + BdfDictionary d = new BdfDictionary(); + for (Map.Entry e : map.entrySet()) { + d.put(e.getKey().getString(), e.getValue()); + } + return d; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java new file mode 100644 index 000000000..67ba5b031 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java @@ -0,0 +1,65 @@ +package org.briarproject.briar.introduction2; + +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; + private final boolean local, read, visible, available, accepted; + + MessageMetadata(MessageType type, @Nullable SessionId sessionId, + long timestamp, boolean local, boolean read, boolean visible, + boolean available, boolean accepted) { + this.type = type; + this.sessionId = sessionId; + this.timestamp = timestamp; + this.local = local; + this.read = read; + this.visible = visible; + this.available = available; + this.accepted = accepted; + } + + MessageType getMessageType() { + return type; + } + + @Nullable + public SessionId getSessionId() { + return sessionId; + } + + long getTimestamp() { + return timestamp; + } + + boolean isLocal() { + return local; + } + + boolean isRead() { + return read; + } + + boolean isVisibleInConversation() { + return visible; + } + + boolean isAvailableToAnswer() { + return available; + } + + public boolean wasAccepted() { + return accepted; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java new file mode 100644 index 000000000..45526a091 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java @@ -0,0 +1,37 @@ +package org.briarproject.briar.introduction2; + +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 MessageParser { + + BdfDictionary getMessagesVisibleInUiQuery(); + + BdfDictionary getInvitesAvailableToAnswerQuery(SessionId sessionId); + + MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException; + + RequestMessage parseRequestMessage(Message m, BdfList body) + throws FormatException; + + AcceptMessage parseAcceptMessage(Message m, BdfList body) + throws FormatException; + + DeclineMessage parseDeclineMessage(Message m, BdfList body) + throws FormatException; + + AuthMessage parseAuthMessage(Message m, BdfList body) + throws FormatException; + + ActivateMessage parseActivateMessage(Message m, BdfList body) + throws FormatException; + + AbortMessage parseAbortMessage(Message m, BdfList body) + throws FormatException; + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java new file mode 100644 index 000000000..1c55be11d --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java @@ -0,0 +1,144 @@ +package org.briarproject.briar.introduction2; + +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.client.MessageTrackerConstants.MSG_KEY_READ; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP; +import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI; +import static org.briarproject.briar.introduction2.MessageType.REQUEST; + +@NotNullByDefault +class MessageParserImpl implements MessageParser { + + private final ClientHelper clientHelper; + + @Inject + MessageParserImpl(ClientHelper clientHelper) { + this.clientHelper = clientHelper; + } + + @Override + public BdfDictionary getMessagesVisibleInUiQuery() { + return BdfDictionary.of(new BdfEntry(MSG_KEY_VISIBLE_IN_UI, true)); + } + + @Override + public BdfDictionary getInvitesAvailableToAnswerQuery(SessionId sessionId) { + return BdfDictionary.of( + new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true), + new BdfEntry(MSG_KEY_MESSAGE_TYPE, 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); + boolean read = d.getBoolean(MSG_KEY_READ); + boolean visible = d.getBoolean(MSG_KEY_VISIBLE_IN_UI); + boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false); + boolean accepted = d.getBoolean(MSG_KEY_INVITATION_ACCEPTED, false); + return new MessageMetadata(type, sessionId, timestamp, local, read, + visible, available, accepted); + } + + @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)); + String message = body.getOptionalString(3); + return new RequestMessage(m.getId(), m.getGroupId(), + m.getTimestamp(), previousMessageId, author, message); + } + + @Override + public AcceptMessage parseAcceptMessage(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); + Map transportProperties = clientHelper + .parseAndValidateTransportPropertiesMap(body.getDictionary(5)); + return new AcceptMessage(m.getId(), m.getGroupId(), m.getTimestamp(), + previousMessageId, sessionId, ephemeralPublicKey, + acceptTimestamp, transportProperties); + } + + @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 AuthMessage parseAuthMessage(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 AuthMessage(m.getId(), m.getGroupId(), m.getTimestamp(), + previousMessageId, sessionId, mac, signature); + } + + @Override + public ActivateMessage parseActivateMessage(Message m, BdfList body) + throws FormatException { + SessionId sessionId = new SessionId(body.getRaw(1)); + byte[] previousMsgBytes = body.getRaw(2); + MessageId previousMessageId = new MessageId(previousMsgBytes); + return new ActivateMessage(m.getId(), m.getGroupId(), m.getTimestamp(), + previousMessageId, sessionId); + } + + @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/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java index fc0109497..0fa094a5e 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java @@ -41,6 +41,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase { private final String text = getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH); private final BdfDictionary meta = new BdfDictionary(); + private final long acceptTimestamp = 42; private final BdfDictionary transportProperties = BdfDictionary.of( new BdfEntry("transportId", new BdfDictionary()) ); @@ -124,7 +125,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase { public void testAcceptsAccept() throws Exception { BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH), - transportProperties); + acceptTimestamp, transportProperties); context.checking(new Expectations() {{ oneOf(clientHelper).parseAndValidateTransportProperties( transportProperties.getDictionary("transportId")); @@ -140,7 +141,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase { public void testRejectsTooShortBodyForAccept() throws Exception { BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), previousMsgId.getBytes(), - getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp); validator.validateMessage(message, group, body); } @@ -148,7 +149,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase { public void testRejectsTooLongBodyForAccept() throws Exception { BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH), - transportProperties, null); + acceptTimestamp, transportProperties, null); validator.validateMessage(message, group, body); } @@ -156,7 +157,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase { public void testRejectsInvalidSessionIdForAccept() throws Exception { BdfList body = BdfList.of(ACCEPT.getValue(), null, previousMsgId.getBytes(), - getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp, transportProperties); validator.validateMessage(message, group, body); } @@ -164,7 +165,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase { @Test(expected = FormatException.class) public void testRejectsInvalidPreviousMsgIdForAccept() throws Exception { BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), - null, getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + null, getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp, transportProperties); validator.validateMessage(message, group, body); } @@ -173,7 +174,8 @@ public class IntroductionValidatorTest extends ValidatorTestCase { public void testRejectsTooLongPublicKeyForAccept() throws Exception { BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), previousMsgId.getBytes(), - getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), transportProperties); + getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp, + transportProperties); validator.validateMessage(message, group, body); } @@ -182,7 +184,8 @@ public class IntroductionValidatorTest extends ValidatorTestCase { throws Exception { BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), previousMsgId.getBytes(), - getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), new BdfDictionary()); + getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp, + new BdfDictionary()); validator.validateMessage(message, group, body); } @@ -397,9 +400,8 @@ public class IntroductionValidatorTest extends ValidatorTestCase { private void expectEncodeRequestMetadata() { context.checking(new Expectations() {{ oneOf(messageEncoder) - .encodeRequestMetadata(REQUEST, message.getTimestamp(), - false, false, - false, false, false); + .encodeRequestMetadata(message.getTimestamp(), false, false, + false, false); will(returnValue(meta)); }}); } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java new file mode 100644 index 000000000..e623f814a --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java @@ -0,0 +1,231 @@ +package org.briarproject.briar.introduction2; + +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.identity.Author; +import org.briarproject.bramble.api.identity.AuthorFactory; +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.bramble.test.BrambleTestCase; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.test.BriarIntegrationTestComponent; +import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent; +import org.junit.Test; + +import java.util.Map; + +import javax.inject.Inject; + +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.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; +import static org.briarproject.bramble.util.StringUtils.getRandomString; +import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; +import static org.briarproject.briar.introduction2.MessageType.ABORT; +import static org.briarproject.briar.introduction2.MessageType.REQUEST; +import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class MessageEncoderParserIntegrationTest extends BrambleTestCase { + + @Inject + ClientHelper clientHelper; + @Inject + MessageFactory messageFactory; + @Inject + AuthorFactory authorFactory; + + private final MessageEncoder messageEncoder; + private final MessageParser messageParser; + + private final GroupId groupId = new GroupId(getRandomId()); + private final long timestamp = 42L; + private final SessionId sessionId = new SessionId(getRandomId()); + private final MessageId previousMsgId = new MessageId(getRandomId()); + private final Author author; + private final String text = + getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH); + private final byte[] ephemeralPublicKey = + getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + private final byte[] mac = getRandomBytes(MAC_BYTES); + private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES); + + public MessageEncoderParserIntegrationTest() { + BriarIntegrationTestComponent component = + DaggerBriarIntegrationTestComponent.builder().build(); + component.inject(this); + + messageEncoder = new MessageEncoderImpl(clientHelper, messageFactory); + messageParser = new MessageParserImpl(clientHelper); + author = getRealAuthor(authorFactory); + } + + @Test + public void testRequestMessageMetadata() throws FormatException { + BdfDictionary d = messageEncoder + .encodeRequestMetadata(timestamp, true, false, false, + true); + MessageMetadata meta = messageParser.parseMetadata(d); + + assertEquals(REQUEST, meta.getMessageType()); + assertNull(meta.getSessionId()); + assertEquals(timestamp, meta.getTimestamp()); + assertTrue(meta.isLocal()); + assertFalse(meta.isRead()); + assertFalse(meta.isVisibleInConversation()); + assertFalse(meta.isAvailableToAnswer()); + assertTrue(meta.wasAccepted()); + } + + @Test + public void testMessageMetadata() throws FormatException { + BdfDictionary d = messageEncoder + .encodeMetadata(ABORT, sessionId, timestamp, false, true, + false); + MessageMetadata meta = messageParser.parseMetadata(d); + + assertEquals(ABORT, meta.getMessageType()); + assertEquals(sessionId, meta.getSessionId()); + assertEquals(timestamp, meta.getTimestamp()); + assertFalse(meta.isLocal()); + assertTrue(meta.isRead()); + assertFalse(meta.isVisibleInConversation()); + assertFalse(meta.isAvailableToAnswer()); + assertFalse(meta.wasAccepted()); + } + + @Test + public void testRequestMessage() throws FormatException { + Message m = messageEncoder + .encodeRequestMessage(groupId, timestamp, previousMsgId, author, + text); + RequestMessage rm = + messageParser.parseRequestMessage(m, clientHelper.toList(m)); + + assertEquals(m.getId(), rm.getMessageId()); + assertEquals(m.getGroupId(), rm.getGroupId()); + assertEquals(m.getTimestamp(), rm.getTimestamp()); + assertEquals(previousMsgId, rm.getPreviousMessageId()); + assertEquals(author, rm.getAuthor()); + assertEquals(text, rm.getMessage()); + } + + @Test + public void testRequestMessageWithPreviousMsgNull() throws FormatException { + Message m = messageEncoder + .encodeRequestMessage(groupId, timestamp, null, author, text); + RequestMessage rm = + messageParser.parseRequestMessage(m, clientHelper.toList(m)); + + assertNull(rm.getPreviousMessageId()); + } + + @Test + public void testRequestMessageWithMsgNull() throws FormatException { + Message m = messageEncoder + .encodeRequestMessage(groupId, timestamp, previousMsgId, author, + null); + RequestMessage rm = + messageParser.parseRequestMessage(m, clientHelper.toList(m)); + + assertNull(rm.getMessage()); + } + + @Test + public void testAcceptMessage() throws Exception { + Map transportProperties = + getTransportPropertiesMap(2); + + long acceptTimestamp = 1337L; + Message m = messageEncoder + .encodeAcceptMessage(groupId, timestamp, previousMsgId, + sessionId, ephemeralPublicKey, acceptTimestamp, + transportProperties); + AcceptMessage rm = + messageParser.parseAcceptMessage(m, clientHelper.toList(m)); + + assertEquals(m.getId(), rm.getMessageId()); + assertEquals(m.getGroupId(), rm.getGroupId()); + assertEquals(m.getTimestamp(), rm.getTimestamp()); + assertEquals(previousMsgId, rm.getPreviousMessageId()); + assertEquals(sessionId, rm.getSessionId()); + assertArrayEquals(ephemeralPublicKey, rm.getEphemeralPublicKey()); + assertEquals(acceptTimestamp, rm.getAcceptTimestamp()); + assertEquals(transportProperties, rm.getTransportProperties()); + } + + @Test + public void testDeclineMessage() throws Exception { + Message m = messageEncoder + .encodeDeclineMessage(groupId, timestamp, previousMsgId, + sessionId); + DeclineMessage rm = + messageParser.parseDeclineMessage(m, clientHelper.toList(m)); + + assertEquals(m.getId(), rm.getMessageId()); + assertEquals(m.getGroupId(), rm.getGroupId()); + assertEquals(m.getTimestamp(), rm.getTimestamp()); + assertEquals(previousMsgId, rm.getPreviousMessageId()); + assertEquals(sessionId, rm.getSessionId()); + } + + @Test + public void testAuthMessage() throws Exception { + Message m = messageEncoder + .encodeAuthMessage(groupId, timestamp, previousMsgId, + sessionId, mac, signature); + AuthMessage rm = + messageParser.parseAuthMessage(m, clientHelper.toList(m)); + + assertEquals(m.getId(), rm.getMessageId()); + assertEquals(m.getGroupId(), rm.getGroupId()); + assertEquals(m.getTimestamp(), rm.getTimestamp()); + assertEquals(previousMsgId, rm.getPreviousMessageId()); + assertEquals(sessionId, rm.getSessionId()); + assertArrayEquals(mac, rm.getMac()); + assertArrayEquals(signature, rm.getSignature()); + } + + @Test + public void testActivateMessage() throws Exception { + Message m = messageEncoder + .encodeActivateMessage(groupId, timestamp, previousMsgId, + sessionId); + ActivateMessage rm = + messageParser.parseActivateMessage(m, clientHelper.toList(m)); + + assertEquals(m.getId(), rm.getMessageId()); + assertEquals(m.getGroupId(), rm.getGroupId()); + assertEquals(m.getTimestamp(), rm.getTimestamp()); + assertEquals(previousMsgId, rm.getPreviousMessageId()); + assertEquals(sessionId, rm.getSessionId()); + } + + @Test + public void testAbortMessage() throws Exception { + Message m = messageEncoder + .encodeAbortMessage(groupId, timestamp, previousMsgId, + sessionId); + AbortMessage rm = + messageParser.parseAbortMessage(m, clientHelper.toList(m)); + + assertEquals(m.getId(), rm.getMessageId()); + assertEquals(m.getGroupId(), rm.getGroupId()); + assertEquals(m.getTimestamp(), rm.getTimestamp()); + assertEquals(previousMsgId, rm.getPreviousMessageId()); + assertEquals(sessionId, rm.getSessionId()); + } + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java new file mode 100644 index 000000000..c7d60662d --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java @@ -0,0 +1,63 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.identity.Author; +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.bramble.test.BrambleMockTestCase; +import org.jmock.Expectations; +import org.junit.Test; + +import static org.briarproject.bramble.test.TestUtils.getAuthor; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.bramble.util.StringUtils.getRandomString; +import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; +import static org.briarproject.briar.introduction2.MessageType.REQUEST; + +public class MessageEncoderTest extends BrambleMockTestCase { + + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final MessageFactory messageFactory = + context.mock(MessageFactory.class); + private final MessageEncoder messageEncoder = + new MessageEncoderImpl(clientHelper, messageFactory); + + private final GroupId groupId = new GroupId(getRandomId()); + private final long timestamp = 42L; + private final Message message = + new Message(new MessageId(getRandomId()), groupId, timestamp, + getRandomBytes(48)); + private final byte[] body = getRandomBytes(42); + private final Author author = getAuthor(); + private final BdfList authorList = new BdfList(); + private final String text = + getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH); + + @Test + public void testEncodeRequestMessage() throws FormatException { + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(author); + will(returnValue(authorList)); + }}); + expectCreateMessage( + BdfList.of(REQUEST.getValue(), null, authorList, text)); + + messageEncoder + .encodeRequestMessage(groupId, timestamp, null, author, text); + } + + private void expectCreateMessage(BdfList bodyList) throws FormatException { + context.checking(new Expectations() {{ + oneOf(clientHelper).toByteArray(bodyList); + will(returnValue(body)); + oneOf(messageFactory).createMessage(groupId, timestamp, body); + will(returnValue(message)); + }}); + } + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index 1b2344d3a..1afcdea7b 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -37,6 +37,7 @@ import org.briarproject.briar.blog.BlogModule; import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.forum.ForumModule; import org.briarproject.briar.introduction.IntroductionModule; +import org.briarproject.briar.introduction2.MessageEncoderParserIntegrationTest; import org.briarproject.briar.messaging.MessagingModule; import org.briarproject.briar.privategroup.PrivateGroupModule; import org.briarproject.briar.privategroup.invitation.GroupInvitationModule; @@ -76,6 +77,8 @@ public interface BriarIntegrationTestComponent { void inject(BriarIntegrationTest init); + void inject(MessageEncoderParserIntegrationTest init); + void inject(BlogModule.EagerSingletons init); void inject(ContactModule.EagerSingletons init); diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java index d29fc0b54..5de39e9f5 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java @@ -1,10 +1,15 @@ package org.briarproject.briar.test; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker.GroupCount; +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.junit.Assert.assertEquals; public class BriarTestUtils { @@ -25,4 +30,9 @@ public class BriarTestUtils { assertEquals(unreadCount, c1.getUnreadCount()); } + public static Author getRealAuthor(AuthorFactory authorFactory) { + return authorFactory.createAuthor(getRandomString(5), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + } + } From e1fae7ad95438d627cc1779f0fe0642f1758c947 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 18 Apr 2018 12:04:33 -0300 Subject: [PATCH 04/21] Implement SessionEncoder and SessionParser --- .../briar/api/introduction2/Role.java | 29 ++ .../introduction2/IntroduceeSession.java | 227 ++++++++++++++ .../briar/introduction2/IntroduceeState.java | 36 +++ .../introduction2/IntroducerSession.java | 115 +++++++ .../briar/introduction2/IntroducerState.java | 36 +++ .../introduction2/IntroductionConstants.java | 29 ++ .../briar/introduction2/PeerSession.java | 25 ++ .../briar/introduction2/Session.java | 37 +++ .../briar/introduction2/SessionEncoder.java | 13 + .../introduction2/SessionEncoderImpl.java | 125 ++++++++ .../briar/introduction2/SessionParser.java | 23 ++ .../introduction2/SessionParserImpl.java | 183 ++++++++++++ .../briar/introduction2/State.java | 7 + .../SessionEncoderParserIntegrationTest.java | 281 ++++++++++++++++++ .../test/BriarIntegrationTestComponent.java | 2 + 15 files changed, 1168 insertions(+) create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/Role.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeSession.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeState.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerSession.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerState.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/PeerSession.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/Session.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoder.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoderImpl.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParser.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParserImpl.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/State.java create mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/SessionEncoderParserIntegrationTest.java diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/Role.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/Role.java new file mode 100644 index 000000000..bc7e4e3d9 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/Role.java @@ -0,0 +1,29 @@ +package org.briarproject.briar.api.introduction2; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public enum Role { + + INTRODUCER(0), INTRODUCEE(1); + + private final int value; + + Role(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static Role fromValue(int value) throws FormatException { + for (Role r : values()) if (r.value == value) return r; + throw new FormatException(); + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeSession.java new file mode 100644 index 000000000..61e301498 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeSession.java @@ -0,0 +1,227 @@ +package org.briarproject.briar.introduction2; + +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.properties.TransportProperties; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.introduction2.Role; + +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_ACTIVATE; +import static org.briarproject.briar.introduction2.IntroduceeState.START; +import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; + +@Immutable +@NotNullByDefault +class IntroduceeSession extends Session + implements PeerSession { + + private final GroupId contactGroupId; + private final long localTimestamp, acceptTimestamp, remoteAcceptTimestamp; + @Nullable + private final MessageId lastLocalMessageId, lastRemoteMessageId; + private final Author introducer, remoteAuthor; + @Nullable + private final byte[] ephemeralPublicKey, ephemeralPrivateKey; + @Nullable + private final byte[] masterKey, remoteEphemeralPublicKey; + @Nullable + private final Map transportProperties; + @Nullable + private final Map + remoteTransportProperties; + @Nullable + private final Map transportKeys; + + IntroduceeSession(SessionId sessionId, IntroduceeState state, + long requestTimestamp, GroupId contactGroupId, + @Nullable MessageId lastLocalMessageId, long localTimestamp, + @Nullable MessageId lastRemoteMessageId, Author introducer, + @Nullable byte[] ephemeralPublicKey, + @Nullable byte[] ephemeralPrivateKey, + @Nullable Map transportProperties, + long acceptTimestamp, @Nullable byte[] masterKey, + Author remoteAuthor, + @Nullable byte[] remoteEphemeralPublicKey, @Nullable + Map remoteTransportProperties, + long remoteAcceptTimestamp, + @Nullable Map transportKeys) { + super(sessionId, state, requestTimestamp); + this.contactGroupId = contactGroupId; + this.lastLocalMessageId = lastLocalMessageId; + this.localTimestamp = localTimestamp; + this.lastRemoteMessageId = lastRemoteMessageId; + this.introducer = introducer; + this.ephemeralPublicKey = ephemeralPublicKey; + this.ephemeralPrivateKey = ephemeralPrivateKey; + this.transportProperties = transportProperties; + this.acceptTimestamp = acceptTimestamp; + this.masterKey = masterKey; + this.remoteAuthor = remoteAuthor; + this.remoteEphemeralPublicKey = remoteEphemeralPublicKey; + this.remoteTransportProperties = remoteTransportProperties; + this.remoteAcceptTimestamp = remoteAcceptTimestamp; + this.transportKeys = transportKeys; + } + + static IntroduceeSession getInitial(GroupId contactGroupId, + SessionId sessionId, Author introducer, Author remoteAuthor) { + return new IntroduceeSession(sessionId, START, -1, contactGroupId, null, + -1, null, introducer, null, null, null, -1, null, remoteAuthor, + null, null, -1, null); + } + + static IntroduceeSession addRemoteRequest(IntroduceeSession s, + IntroduceeState state, RequestMessage m) { + return new IntroduceeSession(s.getSessionId(), state, m.getTimestamp(), + s.contactGroupId, s.lastLocalMessageId, s.localTimestamp, + m.getMessageId(), s.introducer, s.ephemeralPublicKey, + s.ephemeralPrivateKey, s.transportProperties, s.acceptTimestamp, + s.masterKey, s.remoteAuthor, s.remoteEphemeralPublicKey, + s.remoteTransportProperties, s.remoteAcceptTimestamp, + s.transportKeys); + } + + static IntroduceeSession addLocalAccept(IntroduceeSession s, + IntroduceeState state, Message acceptMessage, + byte[] ephemeralPublicKey, byte[] ephemeralPrivateKey, + long acceptTimestamp, + Map transportProperties) { + return new IntroduceeSession(s.getSessionId(), state, + s.getRequestTimestamp(), s.contactGroupId, + acceptMessage.getId(), acceptMessage.getTimestamp(), + s.lastRemoteMessageId, s.introducer, ephemeralPublicKey, + ephemeralPrivateKey, transportProperties, + acceptTimestamp, s.masterKey, s.remoteAuthor, + s.remoteEphemeralPublicKey, s.remoteTransportProperties, + s.remoteAcceptTimestamp, s.transportKeys); + } + + static IntroduceeSession addRemoteAccept(IntroduceeSession s, + IntroduceeState state, AcceptMessage acceptMessage) { + return new IntroduceeSession(s.getSessionId(), state, + s.getRequestTimestamp(), s.contactGroupId, s.lastLocalMessageId, + s.localTimestamp, acceptMessage.getMessageId(), s.introducer, + s.ephemeralPublicKey, s.ephemeralPrivateKey, + s.transportProperties, s.acceptTimestamp, s.masterKey, + s.remoteAuthor, acceptMessage.getEphemeralPublicKey(), + acceptMessage.getTransportProperties(), + acceptMessage.getAcceptTimestamp(), s.transportKeys); + } + + static IntroduceeSession addLocalAuth(IntroduceeSession s, + IntroduceeState state, SecretKey masterKey, Message m) { + return new IntroduceeSession(s.getSessionId(), state, + s.getRequestTimestamp(), s.contactGroupId, m.getId(), + m.getTimestamp(), s.lastRemoteMessageId, s.introducer, + s.ephemeralPublicKey, s.ephemeralPrivateKey, + s.transportProperties, s.acceptTimestamp, masterKey.getBytes(), + s.remoteAuthor, s.remoteEphemeralPublicKey, + s.remoteTransportProperties, s.remoteAcceptTimestamp, + s.transportKeys); + } + + static IntroduceeSession awaitActivate(IntroduceeSession s, AuthMessage m, + Message sent, Map transportKeys) { + return new IntroduceeSession(s.getSessionId(), AWAIT_ACTIVATE, + s.getRequestTimestamp(), s.contactGroupId, sent.getId(), + sent.getTimestamp(), m.getMessageId(), s.introducer, null, null, + null, s.acceptTimestamp, null, s.getRemoteAuthor(), null, null, + s.remoteAcceptTimestamp, transportKeys); + } + + static IntroduceeSession clear(IntroduceeSession s, + @Nullable MessageId lastLocalMessageId, long localTimestamp, + @Nullable MessageId lastRemoteMessageId) { + return new IntroduceeSession(s.getSessionId(), START, + s.getRequestTimestamp(), s.getContactGroupId(), + lastLocalMessageId, localTimestamp, lastRemoteMessageId, + s.getIntroducer(), null, null, null, -1, null, + s.getRemoteAuthor(), null, null, -1, null); + } + + @Override + Role getRole() { + return INTRODUCEE; + } + + public GroupId getContactGroupId() { + return contactGroupId; + } + + public long getLocalTimestamp() { + return localTimestamp; + } + + @Nullable + public MessageId getLastLocalMessageId() { + return lastLocalMessageId; + } + + @Nullable + public MessageId getLastRemoteMessageId() { + return lastRemoteMessageId; + } + + Author getIntroducer() { + return introducer; + } + + @Nullable + byte[] getEphemeralPublicKey() { + return ephemeralPublicKey; + } + + @Nullable + byte[] getEphemeralPrivateKey() { + return ephemeralPrivateKey; + } + + @Nullable + Map getTransportProperties() { + return transportProperties; + } + + long getAcceptTimestamp() { + return acceptTimestamp; + } + + @Nullable + byte[] getMasterKey() { + return masterKey; + } + + Author getRemoteAuthor() { + return remoteAuthor; + } + + @Nullable + byte[] getRemotePublicKey() { + return remoteEphemeralPublicKey; + } + + @Nullable + Map getRemoteTransportProperties() { + return remoteTransportProperties; + } + + long getRemoteAcceptTimestamp() { + return remoteAcceptTimestamp; + } + + @Nullable + Map getTransportKeys() { + return transportKeys; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeState.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeState.java new file mode 100644 index 000000000..4a901752d --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeState.java @@ -0,0 +1,36 @@ +package org.briarproject.briar.introduction2; + +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_RESPONSES(1), + LOCAL_DECLINED(2), + LOCAL_ACCEPTED(3), + REMOTE_ACCEPTED(4), + AWAIT_AUTH(5), + AWAIT_ACTIVATE(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/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerSession.java new file mode 100644 index 000000000..cbfc53923 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerSession.java @@ -0,0 +1,115 @@ +package org.briarproject.briar.introduction2; + +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 org.briarproject.briar.api.introduction2.Role; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; + +@Immutable +@NotNullByDefault +class IntroducerSession extends Session { + + private final Introducee introducee1, introducee2; + + IntroducerSession(SessionId sessionId, IntroducerState state, + long requestTimestamp, Introducee introducee1, + Introducee introducee2) { + super(sessionId, state, requestTimestamp); + this.introducee1 = introducee1; + this.introducee2 = introducee2; + } + + IntroducerSession(SessionId sessionId, GroupId groupId1, Author author1, + GroupId groupId2, Author author2) { + this(sessionId, IntroducerState.START, -1, + new Introducee(sessionId, groupId1, author1), + new Introducee(sessionId, groupId2, author2)); + } + + @Override + Role getRole() { + return INTRODUCER; + } + + Introducee getIntroducee1() { + return introducee1; + } + + Introducee getIntroducee2() { + return introducee2; + } + + @Immutable + @NotNullByDefault + static 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); + } + + private 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/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerState.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerState.java new file mode 100644 index 000000000..43e51d96a --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerState.java @@ -0,0 +1,36 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +enum IntroducerState implements State { + + START(0), + AWAIT_RESPONSES(1), + AWAIT_RESPONSE_A(2), AWAIT_RESPONSE_B(3), + AWAIT_AUTHS(4), + AWAIT_AUTH_A(5), AWAIT_AUTH_B(6), + AWAIT_ACTIVATES(7), + AWAIT_ACTIVATE_A(8), AWAIT_ACTIVATE_B(9); + + private final int value; + + IntroducerState(int value) { + this.value = value; + } + + @Override + public int getValue() { + return value; + } + + static IntroducerState fromValue(int value) throws FormatException { + for (IntroducerState s : values()) if (s.value == value) return s; + throw new FormatException(); + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java index 4098b2421..ea5734468 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java @@ -11,4 +11,33 @@ interface IntroductionConstants { String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer"; String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted"; + // Session Keys + String SESSION_KEY_SESSION_ID = "sessionId"; + 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_1 = "introducee1"; + String SESSION_KEY_INTRODUCEE_2 = "introducee2"; + String SESSION_KEY_GROUP_ID = "groupId"; + String SESSION_KEY_AUTHOR = "author"; + + // Session Keys Introducee + String SESSION_KEY_INTRODUCER = "introducer"; + 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_MASTER_KEY = "masterKey"; + String SESSION_KEY_REMOTE_AUTHOR = "remoteAuthor"; + String SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY = "remoteEphemeralPublicKey"; + String SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES = + "remoteTransportProperties"; + String SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP = "remoteAcceptTimestamp"; + String SESSION_KEY_TRANSPORT_KEYS = "transportKeys"; + } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/PeerSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/PeerSession.java new file mode 100644 index 000000000..e2f8b7c46 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/PeerSession.java @@ -0,0 +1,25 @@ +package org.briarproject.briar.introduction2; + +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/briar-core/src/main/java/org/briarproject/briar/introduction2/Session.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/Session.java new file mode 100644 index 000000000..ca2a89c19 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/Session.java @@ -0,0 +1,37 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.introduction2.Role; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +abstract class Session { + + private final SessionId sessionId; + private final S state; + private long requestTimestamp; + + Session(SessionId sessionId, S state, long requestTimestamp) { + this.sessionId = sessionId; + this.state = state; + this.requestTimestamp = requestTimestamp; + } + + abstract Role getRole(); + + public SessionId getSessionId() { + return sessionId; + } + + S getState() { + return state; + } + + public long getRequestTimestamp() { + return requestTimestamp; + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoder.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoder.java new file mode 100644 index 000000000..4b45c39ca --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoder.java @@ -0,0 +1,13 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@NotNullByDefault +interface SessionEncoder { + + BdfDictionary encodeIntroducerSession(IntroducerSession s); + + BdfDictionary encodeIntroduceeSession(IntroduceeSession s); + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoderImpl.java new file mode 100644 index 000000000..3bce33d85 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoderImpl.java @@ -0,0 +1,125 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.briar.introduction2.IntroducerSession.Introducee; + +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.introduction2.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_AUTHOR; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_GROUP_ID; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCER; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_MASTER_KEY; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_SESSION_ID; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_STATE; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES; + +@Immutable +@NotNullByDefault +class SessionEncoderImpl implements SessionEncoder { + + private final ClientHelper clientHelper; + + @Inject + SessionEncoderImpl(ClientHelper clientHelper) { + this.clientHelper = clientHelper; + } + + @Override + public BdfDictionary encodeIntroducerSession(IntroducerSession s) { + BdfDictionary d = encodeSession(s); + d.put(SESSION_KEY_INTRODUCEE_1, encodeIntroducee(s.getIntroducee1())); + d.put(SESSION_KEY_INTRODUCEE_2, encodeIntroducee(s.getIntroducee2())); + 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(IntroduceeSession s) { + BdfDictionary d = encodeSession(s); + d.put(SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp()); + putNullable(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID, + s.getLastLocalMessageId()); + putNullable(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID, + s.getLastRemoteMessageId()); + d.put(SESSION_KEY_INTRODUCER, clientHelper.toList(s.getIntroducer())); + d.put(SESSION_KEY_REMOTE_AUTHOR, + clientHelper.toList(s.getRemoteAuthor())); + putNullable(d, SESSION_KEY_EPHEMERAL_PUBLIC_KEY, + s.getEphemeralPublicKey()); + putNullable(d, SESSION_KEY_EPHEMERAL_PRIVATE_KEY, + s.getEphemeralPrivateKey()); + putNullable(d, SESSION_KEY_TRANSPORT_PROPERTIES, + s.getTransportProperties() == null ? null : + clientHelper.toDictionary(s.getTransportProperties())); + d.put(SESSION_KEY_ACCEPT_TIMESTAMP, s.getAcceptTimestamp()); + putNullable(d, SESSION_KEY_MASTER_KEY, s.getMasterKey()); + putNullable(d, SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY, + s.getRemotePublicKey()); + putNullable(d, SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES, + s.getRemoteTransportProperties() == null ? null : clientHelper + .toDictionary(s.getRemoteTransportProperties())); + d.put(SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP, s.getRemoteAcceptTimestamp()); + putNullable(d, SESSION_KEY_TRANSPORT_KEYS, + encodeTransportKeys(s.getTransportKeys())); + 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()); + return d; + } + + @Nullable + private BdfDictionary encodeTransportKeys( + @Nullable Map keys) { + if (keys == null) return null; + BdfDictionary d = new BdfDictionary(); + for (Map.Entry 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/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParser.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParser.java new file mode 100644 index 000000000..dd3fba09c --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParser.java @@ -0,0 +1,23 @@ +package org.briarproject.briar.introduction2; + +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.introduction2.Role; + +@NotNullByDefault +interface SessionParser { + + BdfDictionary getSessionQuery(SessionId s); + + Role getRole(BdfDictionary d) throws FormatException; + + IntroducerSession parseIntroducerSession(BdfDictionary d) + throws FormatException; + + IntroduceeSession parseIntroduceeSession(GroupId introducerGroupId, + BdfDictionary d) throws FormatException; + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParserImpl.java new file mode 100644 index 000000000..dbb609fc1 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParserImpl.java @@ -0,0 +1,183 @@ +package org.briarproject.briar.introduction2; + +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.introduction2.Role; +import org.briarproject.briar.introduction2.IntroducerSession.Introducee; + +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.introduction2.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_AUTHOR; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_GROUP_ID; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCER; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_MASTER_KEY; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_SESSION_ID; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_STATE; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES; +import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; +import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; + +@Immutable +@NotNullByDefault +class SessionParserImpl implements SessionParser { + + private final ClientHelper clientHelper; + + @Inject + SessionParserImpl(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 Role.fromValue(d.getLong(SESSION_KEY_ROLE).intValue()); + } + + @Override + public IntroducerSession parseIntroducerSession(BdfDictionary d) + throws FormatException { + if (getRole(d) != INTRODUCER) throw new IllegalArgumentException(); + SessionId sessionId = getSessionId(d); + IntroducerState state = IntroducerState.fromValue(getState(d)); + long requestTimestamp = d.getLong(SESSION_KEY_REQUEST_TIMESTAMP); + Introducee introducee1 = parseIntroducee(sessionId, + d.getDictionary(SESSION_KEY_INTRODUCEE_1)); + Introducee introducee2 = parseIntroducee(sessionId, + d.getDictionary(SESSION_KEY_INTRODUCEE_2)); + return new IntroducerSession(sessionId, state, requestTimestamp, + introducee1, introducee2); + } + + 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 IntroduceeSession parseIntroduceeSession(GroupId introducerGroupId, + BdfDictionary d) throws FormatException { + if (getRole(d) != INTRODUCEE) throw new IllegalArgumentException(); + SessionId sessionId = getSessionId(d); + IntroduceeState state = IntroduceeState.fromValue(getState(d)); + long requestTimestamp = d.getLong(SESSION_KEY_REQUEST_TIMESTAMP); + MessageId lastLocalMessageId = + getMessageId(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID); + long localTimestamp = d.getLong(SESSION_KEY_LOCAL_TIMESTAMP); + MessageId lastRemoteMessageId = + getMessageId(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID); + Author introducer = getAuthor(d, SESSION_KEY_INTRODUCER); + byte[] ephemeralPublicKey = + d.getOptionalRaw(SESSION_KEY_EPHEMERAL_PUBLIC_KEY); + byte[] ephemeralPrivateKey = + d.getOptionalRaw(SESSION_KEY_EPHEMERAL_PRIVATE_KEY); + BdfDictionary tpDict = + d.getOptionalDictionary(SESSION_KEY_TRANSPORT_PROPERTIES); + Map transportProperties = + tpDict == null ? null : clientHelper + .parseAndValidateTransportPropertiesMap(tpDict); + long acceptTimestamp = d.getLong(SESSION_KEY_ACCEPT_TIMESTAMP); + byte[] masterKey = d.getOptionalRaw(SESSION_KEY_MASTER_KEY); + Author remoteAuthor = getAuthor(d, SESSION_KEY_REMOTE_AUTHOR); + byte[] remoteEphemeralPublicKey = + d.getOptionalRaw(SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY); + BdfDictionary rptDict = d.getOptionalDictionary( + SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES); + Map remoteTransportProperties = + rptDict == null ? null : clientHelper + .parseAndValidateTransportPropertiesMap(rptDict); + long remoteAcceptTimestamp = + d.getLong(SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP); + Map transportKeys = parseTransportKeys( + d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS)); + return new IntroduceeSession(sessionId, state, requestTimestamp, + introducerGroupId, lastLocalMessageId, localTimestamp, + lastRemoteMessageId, introducer, ephemeralPublicKey, + ephemeralPrivateKey, transportProperties, acceptTimestamp, + masterKey, remoteAuthor, remoteEphemeralPublicKey, + remoteTransportProperties, remoteAcceptTimestamp, + transportKeys); + } + + private int getState(BdfDictionary d) throws FormatException { + return d.getLong(SESSION_KEY_STATE).intValue(); + } + + 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 parseTransportKeys( + @Nullable BdfDictionary d) throws FormatException { + if (d == null) return null; + Map 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/briar-core/src/main/java/org/briarproject/briar/introduction2/State.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/State.java new file mode 100644 index 000000000..1e1d46e0a --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/State.java @@ -0,0 +1,7 @@ +package org.briarproject.briar.introduction2; + +interface State { + + int getValue(); + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/SessionEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/SessionEncoderParserIntegrationTest.java new file mode 100644 index 000000000..24ddf208a --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/SessionEncoderParserIntegrationTest.java @@ -0,0 +1,281 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.AuthorFactory; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.test.BrambleTestCase; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.introduction2.IntroducerSession.Introducee; +import org.briarproject.briar.test.BriarIntegrationTestComponent; +import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; + +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.bramble.test.TestUtils.getTransportId; +import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; +import static org.briarproject.bramble.util.StringUtils.getRandomString; +import static org.briarproject.briar.introduction2.IntroduceeState.LOCAL_ACCEPTED; +import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTHS; +import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE; +import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; +import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class SessionEncoderParserIntegrationTest extends BrambleTestCase { + + @Inject + ClientHelper clientHelper; + @Inject + AuthorFactory authorFactory; + + private final SessionEncoder sessionEncoder; + private final SessionParser sessionParser; + + private final GroupId groupId1 = new GroupId(getRandomId()); + private final GroupId groupId2 = new GroupId(getRandomId()); + private final SessionId sessionId = new SessionId(getRandomId()); + private final long requestTimestamp = 42; + private final long localTimestamp = 1337; + private final long localTimestamp2 = 1338; + private final long acceptTimestamp = 123456; + private final long remoteAcceptTimestamp = 1234567; + private final MessageId lastLocalMessageId = new MessageId(getRandomId()); + private final MessageId lastLocalMessageId2 = new MessageId(getRandomId()); + private final MessageId lastRemoteMessageId = new MessageId(getRandomId()); + private final MessageId lastRemoteMessageId2 = new MessageId(getRandomId()); + private final Author author1; + private final Author author2; + private final byte[] ephemeralPublicKey = + getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + private final byte[] ephemeralPrivateKey = + getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + private final byte[] masterKey = getRandomBytes(SecretKey.LENGTH); + private final byte[] remoteEphemeralPublicKey = + getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + private final Map transportProperties = + getTransportPropertiesMap(3); + private final Map + remoteTransportProperties = getTransportPropertiesMap(3); + private final Map transportKeys = new HashMap<>(); + + public SessionEncoderParserIntegrationTest() { + BriarIntegrationTestComponent component = + DaggerBriarIntegrationTestComponent.builder().build(); + component.inject(this); + + sessionEncoder = new SessionEncoderImpl(clientHelper); + sessionParser = new SessionParserImpl(clientHelper); + author1 = getRealAuthor(); + author2 = getRealAuthor(); + transportKeys.put(getTransportId(), new KeySetId(1)); + transportKeys.put(getTransportId(), new KeySetId(2)); + transportKeys.put(getTransportId(), new KeySetId(3)); + } + + @Test + public void testIntroducerSession() throws FormatException { + IntroducerSession s1 = getIntroducerSession(); + + BdfDictionary d = sessionEncoder.encodeIntroducerSession(s1); + IntroducerSession s2 = sessionParser.parseIntroducerSession(d); + + assertEquals(INTRODUCER, s1.getRole()); + assertEquals(s1.getRole(), s2.getRole()); + assertEquals(sessionId, s1.getSessionId()); + assertEquals(s1.getSessionId(), s2.getSessionId()); + assertEquals(AWAIT_AUTHS, s1.getState()); + assertEquals(s1.getState(), s2.getState()); + assertIntroduceeEquals(s1.getIntroducee1(), s2.getIntroducee1()); + assertIntroduceeEquals(s1.getIntroducee2(), s2.getIntroducee2()); + } + + @Test + public void testIntroducerSessionWithNulls() throws FormatException { + Introducee introducee1 = + new Introducee(sessionId, groupId1, author1, localTimestamp, + null, null); + Introducee introducee2 = + new Introducee(sessionId, groupId2, author2, localTimestamp2, + null, null); + IntroducerSession s1 = new IntroducerSession(sessionId, + AWAIT_AUTHS, requestTimestamp, introducee1, + introducee2); + + BdfDictionary d = sessionEncoder.encodeIntroducerSession(s1); + IntroducerSession s2 = sessionParser.parseIntroducerSession(d); + + assertNull(s1.getIntroducee1().lastLocalMessageId); + assertEquals(s1.getIntroducee1().lastLocalMessageId, + s2.getIntroducee1().lastLocalMessageId); + assertNull(s1.getIntroducee1().lastRemoteMessageId); + assertEquals(s1.getIntroducee1().lastRemoteMessageId, + s2.getIntroducee1().lastRemoteMessageId); + + assertNull(s1.getIntroducee2().lastLocalMessageId); + assertEquals(s1.getIntroducee2().lastLocalMessageId, + s2.getIntroducee2().lastLocalMessageId); + assertNull(s1.getIntroducee2().lastRemoteMessageId); + assertEquals(s1.getIntroducee2().lastRemoteMessageId, + s2.getIntroducee2().lastRemoteMessageId); + } + + @Test(expected = FormatException.class) + public void testIntroducerSessionUnknownRole() throws FormatException { + IntroducerSession s = getIntroducerSession(); + BdfDictionary d = sessionEncoder.encodeIntroducerSession(s); + d.put(SESSION_KEY_ROLE, 1337); + sessionParser.parseIntroducerSession(d); + } + + @Test(expected = IllegalArgumentException.class) + public void testIntroducerSessionWrongRole() throws FormatException { + IntroducerSession s = getIntroducerSession(); + BdfDictionary d = sessionEncoder.encodeIntroducerSession(s); + d.put(SESSION_KEY_ROLE, INTRODUCEE.getValue()); + sessionParser.parseIntroducerSession(d); + } + + @Test + public void testIntroduceeSession() throws FormatException { + IntroduceeSession s1 = getIntroduceeSession(); + BdfDictionary d = sessionEncoder.encodeIntroduceeSession(s1); + IntroduceeSession s2 = + sessionParser.parseIntroduceeSession(groupId1, d); + + assertEquals(LOCAL_ACCEPTED, s1.getState()); + assertEquals(s1.getState(), s2.getState()); + assertEquals(INTRODUCEE, s1.getRole()); + assertEquals(s1.getRole(), s2.getRole()); + assertEquals(sessionId, s1.getSessionId()); + assertEquals(s1.getSessionId(), s2.getSessionId()); + assertEquals(groupId1, s1.getContactGroupId()); + assertEquals(s1.getContactGroupId(), s2.getContactGroupId()); + assertEquals(localTimestamp, s1.getLocalTimestamp()); + assertEquals(s1.getLocalTimestamp(), s2.getLocalTimestamp()); + assertEquals(lastLocalMessageId, s1.getLastLocalMessageId()); + assertEquals(s1.getLastLocalMessageId(), s2.getLastLocalMessageId()); + assertEquals(lastRemoteMessageId, s1.getLastRemoteMessageId()); + assertEquals(s1.getLastRemoteMessageId(), s2.getLastRemoteMessageId()); + assertEquals(author1, s1.getIntroducer()); + assertEquals(s1.getIntroducer(), s2.getIntroducer()); + assertEquals(author2, s1.getRemoteAuthor()); + assertEquals(s1.getRemoteAuthor(), s2.getRemoteAuthor()); + assertArrayEquals(ephemeralPublicKey, s1.getEphemeralPublicKey()); + assertArrayEquals(s1.getEphemeralPublicKey(), + s2.getEphemeralPublicKey()); + assertArrayEquals(ephemeralPrivateKey, s1.getEphemeralPrivateKey()); + assertArrayEquals(s1.getEphemeralPrivateKey(), + s2.getEphemeralPrivateKey()); + assertEquals(acceptTimestamp, s1.getAcceptTimestamp()); + assertEquals(s1.getAcceptTimestamp(), s2.getAcceptTimestamp()); + assertArrayEquals(masterKey, s1.getMasterKey()); + assertArrayEquals(s1.getMasterKey(), s2.getMasterKey()); + assertArrayEquals(remoteEphemeralPublicKey, s1.getRemotePublicKey()); + assertArrayEquals(s1.getRemotePublicKey(), + s2.getRemotePublicKey()); + assertEquals(transportProperties, s1.getTransportProperties()); + assertEquals(s1.getTransportProperties(), s2.getTransportProperties()); + assertEquals(remoteTransportProperties, + s1.getRemoteTransportProperties()); + assertEquals(s1.getRemoteTransportProperties(), + s2.getRemoteTransportProperties()); + assertEquals(remoteAcceptTimestamp, s1.getRemoteAcceptTimestamp()); + assertEquals(s1.getRemoteAcceptTimestamp(), s2.getRemoteAcceptTimestamp()); + assertEquals(transportKeys, s1.getTransportKeys()); + assertEquals(s1.getTransportKeys(), s2.getTransportKeys()); + } + + @Test + public void testIntroduceeSessionWithNulls() throws FormatException { + IntroduceeSession s1 = + new IntroduceeSession(sessionId, LOCAL_ACCEPTED, + requestTimestamp, groupId1, null, localTimestamp, null, + author1, null, null, null, acceptTimestamp, null, + author2, null, null, remoteAcceptTimestamp, null); + + BdfDictionary d = sessionEncoder.encodeIntroduceeSession(s1); + IntroduceeSession s2 = + sessionParser.parseIntroduceeSession(groupId1, d); + + assertNull(s1.getLastLocalMessageId()); + assertEquals(s1.getLastLocalMessageId(), s2.getLastLocalMessageId()); + assertNull(s1.getLastRemoteMessageId()); + assertEquals(s1.getLastRemoteMessageId(), s2.getLastRemoteMessageId()); + assertNull(s1.getEphemeralPublicKey()); + assertArrayEquals(s1.getEphemeralPublicKey(), + s2.getEphemeralPublicKey()); + assertNull(s1.getEphemeralPrivateKey()); + assertArrayEquals(s1.getEphemeralPrivateKey(), + s2.getEphemeralPrivateKey()); + assertNull(s1.getTransportKeys()); + assertEquals(s1.getTransportKeys(), s2.getTransportKeys()); + } + + @Test(expected = FormatException.class) + public void testIntroduceeSessionUnknownRole() throws FormatException { + IntroduceeSession s = getIntroduceeSession(); + BdfDictionary d = sessionEncoder.encodeIntroduceeSession(s); + d.put(SESSION_KEY_ROLE, 1337); + sessionParser.parseIntroduceeSession(groupId1, d); + } + + @Test(expected = IllegalArgumentException.class) + public void testIntroduceeSessionWrongRole() throws FormatException { + IntroduceeSession s = getIntroduceeSession(); + BdfDictionary d = sessionEncoder.encodeIntroduceeSession(s); + d.put(SESSION_KEY_ROLE, INTRODUCER.getValue()); + sessionParser.parseIntroduceeSession(groupId1, d); + } + + private IntroducerSession getIntroducerSession() { + Introducee introducee1 = + new Introducee(sessionId, groupId1, author1, localTimestamp, + lastLocalMessageId, lastRemoteMessageId); + Introducee introducee2 = + new Introducee(sessionId, groupId2, author2, localTimestamp2, + lastLocalMessageId2, lastRemoteMessageId2); + return new IntroducerSession(sessionId, AWAIT_AUTHS, + requestTimestamp, introducee1, introducee2); + } + + private IntroduceeSession getIntroduceeSession() { + return new IntroduceeSession(sessionId, LOCAL_ACCEPTED, + requestTimestamp, groupId1, lastLocalMessageId, localTimestamp, + lastRemoteMessageId, author1, ephemeralPublicKey, + ephemeralPrivateKey, transportProperties, acceptTimestamp, + masterKey, author2, remoteEphemeralPublicKey, + remoteTransportProperties, remoteAcceptTimestamp, + transportKeys); + } + + private void assertIntroduceeEquals(Introducee i1, Introducee i2) { + assertEquals(i1.author, i2.author); + assertEquals(i1.groupId, i2.groupId); + assertEquals(i1.localTimestamp, i2.localTimestamp); + assertEquals(i1.lastLocalMessageId, i2.lastLocalMessageId); + assertEquals(i1.lastRemoteMessageId, i2.lastRemoteMessageId); + } + + private Author getRealAuthor() { + return authorFactory.createAuthor(getRandomString(5), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); + } + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index 1afcdea7b..54709815e 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -38,6 +38,7 @@ import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.forum.ForumModule; import org.briarproject.briar.introduction.IntroductionModule; import org.briarproject.briar.introduction2.MessageEncoderParserIntegrationTest; +import org.briarproject.briar.introduction2.SessionEncoderParserIntegrationTest; import org.briarproject.briar.messaging.MessagingModule; import org.briarproject.briar.privategroup.PrivateGroupModule; import org.briarproject.briar.privategroup.invitation.GroupInvitationModule; @@ -78,6 +79,7 @@ public interface BriarIntegrationTestComponent { void inject(BriarIntegrationTest init); void inject(MessageEncoderParserIntegrationTest init); + void inject(SessionEncoderParserIntegrationTest init); void inject(BlogModule.EagerSingletons init); From d57102ed909da731021c8b1e2ac50adf5d2e8c68 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Sat, 21 Apr 2018 17:21:38 -0300 Subject: [PATCH 05/21] IntroductionCrypto: Create dedicated class to handle introduction related crypto --- .../introduction2/IntroductionConstants.java | 16 ++ .../introduction2/IntroductionCrypto.java | 88 +++++++ .../introduction2/IntroductionCryptoImpl.java | 216 ++++++++++++++++++ .../IntroductionCryptoImplTest.java | 142 ++++++++++++ .../introduction2/IntroductionCryptoTest.java | 46 ++++ .../test/BriarIntegrationTestComponent.java | 2 + 6 files changed, 510 insertions(+) create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java create mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java create mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java index 6b91b33c0..962bd0a52 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java @@ -10,4 +10,20 @@ public interface IntroductionConstants { */ int MAX_REQUEST_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; + String LABEL_SESSION_ID = "org.briarproject.briar.introduction/SESSION_ID"; + + String LABEL_MASTER_KEY = "org.briarproject.briar.introduction/MASTER_KEY"; + + String LABEL_ALICE_MAC_KEY = + "org.briarproject.briar.introduction/ALICE_MAC_KEY"; + + String LABEL_BOB_MAC_KEY = + "org.briarproject.briar.introduction/BOB_MAC_KEY"; + + String LABEL_AUTH_MAC = "org.briarproject.briar.introduction/AUTH_MAC"; + + String LABEL_AUTH_SIGN = "org.briarproject.briar.introduction/AUTH_SIGN"; + + String LABEL_AUTH_NONCE = "org.briarproject.briar.introduction/AUTH_NONCE"; + } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java new file mode 100644 index 000000000..a91184db2 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java @@ -0,0 +1,88 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.FormatException; +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 IntroductionCrypto { + + /** + * Returns the {@link SessionId} based on the introducer + * and the two introducees. + * + * Note: The roles of Alice and Bob can be switched. + */ + SessionId getSessionId(Author introducer, Author alice, Author bob); + + /** + * Returns true if the first author is indeed alice + */ + boolean isAlice(AuthorId alice, AuthorId bob); + + /** + * Generates an agreement key pair. + */ + KeyPair generateKeyPair(); + + /** + * Derives a session master key for Alice or Bob. + * + * @param alice true if the session owner is Alice + * @return The secret master key + */ + SecretKey deriveMasterKey(IntroduceeSession s, 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(IntroduceeSession, boolean)} + * @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 and + * transport properties. + */ + byte[] mac(SecretKey macKey, IntroduceeSession s, AuthorId localAuthorId, + boolean alice) throws FormatException; + + /** + * Verifies a received MAC + * + * @param mac The MAC to verify + * as returned by {@link #deriveMasterKey(IntroduceeSession, boolean)} + * @throws GeneralSecurityException if the verification fails + */ + void verifyMac(byte[] mac, IntroduceeSession s, AuthorId localAuthorId) + throws GeneralSecurityException, FormatException; + + /** + * 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 corresponding MAC key. + * + * @throws GeneralSecurityException if the signature is invalid + */ + void verifySignature(byte[] signature, IntroduceeSession s, + AuthorId localAuthorId) throws GeneralSecurityException; + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java new file mode 100644 index 000000000..93ff9b33e --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java @@ -0,0 +1,216 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.Bytes; +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.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.briar.api.client.SessionId; + +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Map; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_ALICE_MAC_KEY; +import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_MAC; +import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_NONCE; +import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_SIGN; +import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_BOB_MAC_KEY; +import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_MASTER_KEY; +import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID; +import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION; + +@Immutable +@NotNullByDefault +class IntroductionCryptoImpl implements IntroductionCrypto { + + private final CryptoComponent crypto; + private final ClientHelper clientHelper; + + @Inject + IntroductionCryptoImpl( + CryptoComponent crypto, + ClientHelper clientHelper) { + this.crypto = crypto; + this.clientHelper = clientHelper; + } + + @Override + public SessionId getSessionId(Author introducer, Author alice, + Author bob) { + boolean isAlice = isAlice(alice.getId(), bob.getId()); + byte[] hash = crypto.hash( + LABEL_SESSION_ID, + introducer.getId().getBytes(), + isAlice ? alice.getId().getBytes() : bob.getId().getBytes(), + isAlice ? bob.getId().getBytes() : alice.getId().getBytes() + ); + return new SessionId(hash); + } + + @Override + public KeyPair generateKeyPair() { + return crypto.generateAgreementKeyPair(); + } + + @Override + public boolean isAlice(AuthorId alice, AuthorId bob) { + byte[] a = alice.getBytes(); + byte[] b = bob.getBytes(); + return Bytes.COMPARATOR.compare(new Bytes(a), new Bytes(b)) < 0; + } + + @Override + @SuppressWarnings("ConstantConditions") + public SecretKey deriveMasterKey(IntroduceeSession s, boolean alice) + throws GeneralSecurityException { + return deriveMasterKey(s.getEphemeralPublicKey(), + s.getEphemeralPrivateKey(), s.getRemotePublicKey(), alice); + } + + 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[] {CLIENT_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[] mac(SecretKey macKey, IntroduceeSession s, + AuthorId localAuthorId, boolean alice) throws FormatException { + return mac(macKey, s.getIntroducer().getId(), localAuthorId, + s.getRemoteAuthor().getId(), s.getAcceptTimestamp(), + s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(), + s.getRemotePublicKey(), s.getTransportProperties(), + s.getRemoteTransportProperties(), alice); + } + + byte[] mac(SecretKey macKey, AuthorId introducerId, + AuthorId localAuthorId, AuthorId remoteAuthorId, + long acceptTimestamp, long remoteAcceptTimestamp, + byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey, + Map transportProperties, + Map remoteTransportProperties, + boolean alice) throws FormatException { + BdfList localInfo = BdfList.of( + localAuthorId, + acceptTimestamp, + ephemeralPublicKey, + clientHelper.toDictionary(transportProperties) + ); + BdfList remoteInfo = BdfList.of( + remoteAuthorId, + remoteAcceptTimestamp, + remoteEphemeralPublicKey, + clientHelper.toDictionary(remoteTransportProperties) + ); + BdfList macList = BdfList.of( + introducerId, + alice ? localInfo : remoteInfo, + alice ? remoteInfo : localInfo + ); + return crypto.mac( + LABEL_AUTH_MAC, + macKey, + clientHelper.toByteArray(macList) + ); + } + + @Override + @SuppressWarnings("ConstantConditions") + public void verifyMac(byte[] mac, IntroduceeSession s, + AuthorId localAuthorId) + throws GeneralSecurityException, FormatException { + boolean alice = isAlice(localAuthorId, s.getRemoteAuthor().getId()); + verifyMac(mac, new SecretKey(s.getMasterKey()), + s.getIntroducer().getId(), localAuthorId, + s.getRemoteAuthor().getId(), s.getAcceptTimestamp(), + s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(), + s.getRemotePublicKey(), s.getTransportProperties(), + s.getRemoteTransportProperties(), !alice); + } + + void verifyMac(byte[] mac, SecretKey masterKey, + AuthorId introducerId, AuthorId localAuthorId, + AuthorId remoteAuthorId, long acceptTimestamp, + long remoteAcceptTimestamp, byte[] ephemeralPublicKey, + byte[] remoteEphemeralPublicKey, + Map transportProperties, + Map remoteTransportProperties, + boolean alice) throws GeneralSecurityException, FormatException { + SecretKey macKey = deriveMacKey(masterKey, alice); + byte[] calculatedMac = + mac(macKey, introducerId, localAuthorId, remoteAuthorId, + acceptTimestamp, remoteAcceptTimestamp, + ephemeralPublicKey, remoteEphemeralPublicKey, + transportProperties, remoteTransportProperties, !alice); + if (!Arrays.equals(mac, calculatedMac)) { + throw new GeneralSecurityException(); + } + } + + @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, IntroduceeSession s, + AuthorId localAuthorId) throws GeneralSecurityException { + boolean alice = isAlice(s.getRemoteAuthor().getId(), localAuthorId); + SecretKey macKey = deriveMacKey(new SecretKey(s.getMasterKey()), alice); + verifySignature(macKey, s.getRemoteAuthor().getPublicKey(), signature); + } + + void verifySignature(SecretKey macKey, byte[] publicKey, + byte[] signature) throws GeneralSecurityException { + byte[] nonce = getNonce(macKey); + if (!crypto.verify(LABEL_AUTH_SIGN, nonce, publicKey, signature)) { + throw new GeneralSecurityException(); + } + } + + private byte[] getNonce(SecretKey macKey) { + return crypto.mac(LABEL_AUTH_NONCE, macKey); + } + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java new file mode 100644 index 000000000..57cadce0e --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java @@ -0,0 +1,142 @@ +package org.briarproject.briar.introduction2; + +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.SecretKey; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.AuthorFactory; +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.test.BrambleTestCase; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.test.BriarIntegrationTestComponent; +import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent; +import org.junit.Test; + +import java.util.Map; + +import javax.inject.Inject; + +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; +import static org.briarproject.bramble.util.StringUtils.fromHexString; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class IntroductionCryptoImplTest extends BrambleTestCase { + + @Inject + ClientHelper clientHelper; + @Inject + AuthorFactory authorFactory; + @Inject + CryptoComponent cryptoComponent; + + private final IntroductionCryptoImpl crypto; + + private final Author introducer; + private final LocalAuthor alice, bob; + private final long aliceAcceptTimestamp = 42L; + private final long bobAcceptTimestamp = 1337L; + private final SecretKey masterKey = + new SecretKey(getRandomBytes(SecretKey.LENGTH)); + private final KeyPair aliceEphemeral, bobEphemeral; + private final Map aliceTransport = + getTransportPropertiesMap(3); + private final Map bobTransport = + getTransportPropertiesMap(3); + + public IntroductionCryptoImplTest() { + BriarIntegrationTestComponent component = + DaggerBriarIntegrationTestComponent.builder().build(); + component.inject(this); + crypto = new IntroductionCryptoImpl(cryptoComponent, clientHelper); + + // create actual deterministic authors for testing + introducer = authorFactory + .createAuthor("Introducer", new byte[] {0x1, 0x2, 0x3}); + alice = authorFactory.createLocalAuthor("Alice", + fromHexString( + "A626F080C94771698F86B4B4094C4F560904B53398805AE02BA2343F1829187A"), + fromHexString( + "60F010187AF91ACA15141E8C811EC8E79C7CAA6461C21A852BB03066C89B0A70")); + bob = authorFactory.createLocalAuthor("Bob", + fromHexString( + "A0D0FED1CE4674D8B6441AD0A664E41BF60D489F35DA11F52AF923540848546F"), + fromHexString( + "20B25BE7E999F68FE07189449E91984FA79121DBFF28A651669A3CF512D6A758")); + aliceEphemeral = crypto.generateKeyPair(); + bobEphemeral = crypto.generateKeyPair(); + } + + @Test + public void testGetSessionId() { + SessionId s1 = crypto.getSessionId(introducer, alice, bob); + SessionId s2 = crypto.getSessionId(introducer, bob, alice); + assertEquals(s1, s2); + } + + @Test + public void testIsAlice() { + assertTrue(crypto.isAlice(alice.getId(), bob.getId())); + assertFalse(crypto.isAlice(bob.getId(), alice.getId())); + } + + @Test + public void testDeriveMasterKey() throws Exception { + SecretKey aliceMasterKey = crypto.deriveMasterKey(alice.getPublicKey(), + alice.getPrivateKey(), bob.getPublicKey(), true); + SecretKey bobMasterKey = crypto.deriveMasterKey(bob.getPublicKey(), + bob.getPrivateKey(), alice.getPublicKey(), false); + assertArrayEquals(aliceMasterKey.getBytes(), bobMasterKey.getBytes()); + } + + @Test + public void testAliceMac() throws Exception { + SecretKey aliceMacKey = crypto.deriveMacKey(masterKey, true); + byte[] aliceMac = + crypto.mac(aliceMacKey, introducer.getId(), alice.getId(), + bob.getId(), aliceAcceptTimestamp, bobAcceptTimestamp, + aliceEphemeral.getPublic().getEncoded(), + bobEphemeral.getPublic().getEncoded(), aliceTransport, + bobTransport, true); + + crypto.verifyMac(aliceMac, masterKey, introducer.getId(), bob.getId(), + alice.getId(), bobAcceptTimestamp, aliceAcceptTimestamp, + bobEphemeral.getPublic().getEncoded(), + aliceEphemeral.getPublic().getEncoded(), bobTransport, + aliceTransport, true); + } + + @Test + public void testBobMac() throws Exception { + SecretKey bobMacKey = crypto.deriveMacKey(masterKey, false); + byte[] bobMac = + crypto.mac(bobMacKey, introducer.getId(), bob.getId(), + alice.getId(), bobAcceptTimestamp, aliceAcceptTimestamp, + bobEphemeral.getPublic().getEncoded(), + aliceEphemeral.getPublic().getEncoded(), bobTransport, + aliceTransport, false); + + crypto.verifyMac(bobMac, masterKey, introducer.getId(), alice.getId(), + bob.getId(), aliceAcceptTimestamp, bobAcceptTimestamp, + aliceEphemeral.getPublic().getEncoded(), + bobEphemeral.getPublic().getEncoded(), aliceTransport, + bobTransport, false); + } + + @Test + public void testSign() throws Exception { + KeyPair keyPair = cryptoComponent.generateSignatureKeyPair(); + SecretKey macKey = crypto.deriveMacKey(masterKey, true); + byte[] signature = + crypto.sign(macKey, keyPair.getPrivate().getEncoded()); + crypto.verifySignature(macKey, keyPair.getPublic().getEncoded(), + signature); + } + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java new file mode 100644 index 000000000..139c4ca40 --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java @@ -0,0 +1,46 @@ +package org.briarproject.briar.introduction2; + +import org.briarproject.bramble.api.UniqueId; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.briarproject.briar.api.client.SessionId; +import org.jmock.Expectations; +import org.junit.Test; + +import static org.briarproject.bramble.test.TestUtils.getAuthor; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID; +import static org.junit.Assert.assertEquals; + +public class IntroductionCryptoTest extends BrambleMockTestCase { + + private final CryptoComponent cryptoComponent = + context.mock(CryptoComponent.class); + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + + private final IntroductionCrypto crypto = + new IntroductionCryptoImpl(cryptoComponent, clientHelper); + + private final Author introducer = getAuthor(); + private final Author alice = getAuthor(), bob = getAuthor(); + private final byte[] hash = getRandomBytes(UniqueId.LENGTH); + + @Test + public void testGetSessionId() { + boolean isAlice = crypto.isAlice(alice.getId(), bob.getId()); + context.checking(new Expectations() {{ + oneOf(cryptoComponent).hash( + LABEL_SESSION_ID, + introducer.getId().getBytes(), + isAlice ? alice.getId().getBytes() : bob.getId().getBytes(), + isAlice ? bob.getId().getBytes() : alice.getId().getBytes() + ); + will(returnValue(hash)); + }}); + SessionId sessionId = crypto.getSessionId(introducer, alice, bob); + assertEquals(new SessionId(hash), sessionId); + } + +} diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index 54709815e..fe2e745c0 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -37,6 +37,7 @@ import org.briarproject.briar.blog.BlogModule; import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.forum.ForumModule; import org.briarproject.briar.introduction.IntroductionModule; +import org.briarproject.briar.introduction2.IntroductionCryptoImplTest; import org.briarproject.briar.introduction2.MessageEncoderParserIntegrationTest; import org.briarproject.briar.introduction2.SessionEncoderParserIntegrationTest; import org.briarproject.briar.messaging.MessagingModule; @@ -80,6 +81,7 @@ public interface BriarIntegrationTestComponent { void inject(MessageEncoderParserIntegrationTest init); void inject(SessionEncoderParserIntegrationTest init); + void inject(IntroductionCryptoImplTest init); void inject(BlogModule.EagerSingletons init); From 61b216f572e653879e6c56cd54e1e5016a781b8d Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 23 Apr 2018 13:55:01 -0300 Subject: [PATCH 06/21] Copy over Introduction API messages and events from old client --- .../introduction2/IntroductionMessage.java | 43 ++++++++++++++++++ .../introduction2/IntroductionRequest.java | 44 +++++++++++++++++++ .../introduction2/IntroductionResponse.java | 35 +++++++++++++++ .../event/IntroductionAbortedEvent.java | 32 ++++++++++++++ .../IntroductionRequestReceivedEvent.java | 32 ++++++++++++++ .../IntroductionResponseReceivedEvent.java | 31 +++++++++++++ .../event/IntroductionSucceededEvent.java | 23 ++++++++++ 7 files changed, 240 insertions(+) create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionMessage.java create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionRequest.java create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionResponse.java create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionAbortedEvent.java create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionRequestReceivedEvent.java create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionResponseReceivedEvent.java create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionSucceededEvent.java diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionMessage.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionMessage.java new file mode 100644 index 000000000..a4b3999d1 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionMessage.java @@ -0,0 +1,43 @@ +package org.briarproject.briar.api.introduction2; + +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.BaseMessageHeader; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.concurrent.Immutable; + +import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; + +@Immutable +@NotNullByDefault +public class IntroductionMessage extends BaseMessageHeader { + + private final SessionId sessionId; + private final MessageId messageId; + private final Role role; + + IntroductionMessage(SessionId sessionId, MessageId messageId, + GroupId groupId, Role role, long time, boolean local, boolean sent, + boolean seen, boolean read) { + + super(messageId, groupId, time, local, sent, seen, read); + this.sessionId = sessionId; + this.messageId = messageId; + this.role = role; + } + + public SessionId getSessionId() { + return sessionId; + } + + public MessageId getMessageId() { + return messageId; + } + + public boolean isIntroducer() { + return role == INTRODUCER; + } + +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionRequest.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionRequest.java new file mode 100644 index 000000000..4494fa123 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionRequest.java @@ -0,0 +1,44 @@ +package org.briarproject.briar.api.introduction2; + +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 +public class IntroductionRequest extends IntroductionResponse { + + @Nullable + private final String message; + private final boolean answered, exists; + + public IntroductionRequest(SessionId sessionId, MessageId messageId, + GroupId groupId, Role role, long time, boolean local, boolean sent, + boolean seen, boolean read, String name, boolean accepted, + @Nullable String message, boolean answered, boolean exists) { + + super(sessionId, messageId, groupId, role, time, local, sent, seen, + read, name, accepted); + + this.message = message; + this.answered = answered; + this.exists = exists; + } + + @Nullable + public String getMessage() { + return message; + } + + public boolean wasAnswered() { + return answered; + } + + public boolean contactExists() { + return exists; + } +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionResponse.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionResponse.java new file mode 100644 index 000000000..b9d2bd993 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionResponse.java @@ -0,0 +1,35 @@ +package org.briarproject.briar.api.introduction2; + +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.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class IntroductionResponse extends IntroductionMessage { + + private final String name; + private final boolean accepted; + + public IntroductionResponse(SessionId sessionId, MessageId messageId, + GroupId groupId, Role role, long time, boolean local, boolean sent, + boolean seen, boolean read, String name, boolean accepted) { + super(sessionId, messageId, groupId, role, time, local, sent, seen, + read); + + this.name = name; + this.accepted = accepted; + } + + public String getName() { + return name; + } + + public boolean wasAccepted() { + return accepted; + } + +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionAbortedEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionAbortedEvent.java new file mode 100644 index 000000000..610db3970 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionAbortedEvent.java @@ -0,0 +1,32 @@ +package org.briarproject.briar.api.introduction2.event; + +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.identity.AuthorId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.api.client.SessionId; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +// TODO still needed? +public class IntroductionAbortedEvent extends Event { + + private final AuthorId remoteAuthorId; + private final SessionId sessionId; + + public IntroductionAbortedEvent(AuthorId remoteAuthorId, + SessionId sessionId) { + this.remoteAuthorId = remoteAuthorId; + this.sessionId = sessionId; + } + + public AuthorId getRemoteAuthorId() { + return remoteAuthorId; + } + + public SessionId getSessionId() { + return sessionId; + } + +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionRequestReceivedEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionRequestReceivedEvent.java new file mode 100644 index 000000000..0798ac356 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionRequestReceivedEvent.java @@ -0,0 +1,32 @@ +package org.briarproject.briar.api.introduction2.event; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.api.introduction2.IntroductionRequest; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class IntroductionRequestReceivedEvent extends Event { + + private final ContactId contactId; + private final IntroductionRequest introductionRequest; + + public IntroductionRequestReceivedEvent(ContactId contactId, + IntroductionRequest introductionRequest) { + + this.contactId = contactId; + this.introductionRequest = introductionRequest; + } + + public ContactId getContactId() { + return contactId; + } + + public IntroductionRequest getIntroductionRequest() { + return introductionRequest; + } + +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionResponseReceivedEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionResponseReceivedEvent.java new file mode 100644 index 000000000..a05731b59 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionResponseReceivedEvent.java @@ -0,0 +1,31 @@ +package org.briarproject.briar.api.introduction2.event; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.api.introduction2.IntroductionResponse; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class IntroductionResponseReceivedEvent extends Event { + + private final ContactId contactId; + private final IntroductionResponse introductionResponse; + + public IntroductionResponseReceivedEvent(ContactId contactId, + IntroductionResponse introductionResponse) { + + this.contactId = contactId; + this.introductionResponse = introductionResponse; + } + + public ContactId getContactId() { + return contactId; + } + + public IntroductionResponse getIntroductionResponse() { + return introductionResponse; + } +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionSucceededEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionSucceededEvent.java new file mode 100644 index 000000000..11f85c2ce --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionSucceededEvent.java @@ -0,0 +1,23 @@ +package org.briarproject.briar.api.introduction2.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 +// TODO still needed? +public class IntroductionSucceededEvent extends Event { + + private final Contact contact; + + public IntroductionSucceededEvent(Contact contact) { + this.contact = contact; + } + + public Contact getContact() { + return contact; + } +} From 1bc29fec067ef00113eacae52489bf080fee2de7 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 19 Apr 2018 17:06:31 -0300 Subject: [PATCH 07/21] IntroductionManager and Protocol Engines --- .../introduction2/IntroductionManager.java | 52 ++ .../briar/client/BdfIncomingMessageHook.java | 1 + .../introduction2/AbstractProtocolEngine.java | 197 +++++++ .../IntroduceeProtocolEngine.java | 510 ++++++++++++++++++ .../IntroducerProtocolEngine.java | 462 ++++++++++++++++ .../introduction2/IntroductionConstants.java | 3 + .../IntroductionManagerImpl.java | 459 ++++++++++++++++ .../briar/introduction2/ProtocolEngine.java | 40 ++ 8 files changed, 1724 insertions(+) create mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionManager.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/AbstractProtocolEngine.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeProtocolEngine.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerProtocolEngine.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionManagerImpl.java create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/ProtocolEngine.java diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionManager.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionManager.java new file mode 100644 index 000000000..f3d5c40fa --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionManager.java @@ -0,0 +1,52 @@ +package org.briarproject.briar.api.introduction2; + +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.ClientId; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.messaging.ConversationManager.ConversationClient; + +import java.util.Collection; + +import javax.annotation.Nullable; + +@NotNullByDefault +public interface IntroductionManager extends ConversationClient { + + /** + * The unique ID of the introduction client. + */ + ClientId CLIENT_ID = new ClientId("org.briarproject.briar.introduction"); + + /** + * The current version of the introduction client. + */ + int CLIENT_VERSION = 1; + + /** + * Sends two initial introduction messages. + */ + void makeIntroduction(Contact c1, Contact c2, @Nullable String msg, + long timestamp) throws DbException; + + /** + * Accepts an introduction. + */ + void acceptIntroduction(ContactId contactId, SessionId sessionId, + long timestamp) throws DbException; + + /** + * Declines an introduction. + */ + void declineIntroduction(ContactId contactId, SessionId sessionId, + long timestamp) throws DbException; + + /** + * Returns all introduction messages for the given contact. + */ + Collection getIntroductionMessages(ContactId contactId) + throws DbException; + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java b/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java index 19d8142c1..b7e16c8d6 100644 --- a/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java +++ b/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java @@ -40,6 +40,7 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook, /** * 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 diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/AbstractProtocolEngine.java new file mode 100644 index 000000000..d2c9f2270 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/AbstractProtocolEngine.java @@ -0,0 +1,197 @@ +package org.briarproject.briar.introduction2; + +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.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.MessageTracker; +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.api.introduction2.IntroductionManager.CLIENT_ID; +import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION; +import static org.briarproject.briar.introduction2.MessageType.ABORT; +import static org.briarproject.briar.introduction2.MessageType.ACCEPT; +import static org.briarproject.briar.introduction2.MessageType.ACTIVATE; +import static org.briarproject.briar.introduction2.MessageType.AUTH; +import static org.briarproject.briar.introduction2.MessageType.DECLINE; +import static org.briarproject.briar.introduction2.MessageType.REQUEST; + +@Immutable +@NotNullByDefault +abstract class AbstractProtocolEngine + implements ProtocolEngine { + + protected final DatabaseComponent db; + protected final ClientHelper clientHelper; + protected final ContactManager contactManager; + protected final ContactGroupFactory contactGroupFactory; + protected final MessageTracker messageTracker; + protected final IdentityManager identityManager; + protected final MessageParser messageParser; + protected final MessageEncoder messageEncoder; + protected final Clock clock; + + AbstractProtocolEngine( + DatabaseComponent db, + ClientHelper clientHelper, + ContactManager contactManager, + ContactGroupFactory contactGroupFactory, + MessageTracker messageTracker, + IdentityManager identityManager, + MessageParser messageParser, + MessageEncoder messageEncoder, + Clock clock) { + this.db = db; + this.clientHelper = clientHelper; + this.contactManager = contactManager; + this.contactGroupFactory = contactGroupFactory; + this.messageTracker = messageTracker; + this.identityManager = identityManager; + this.messageParser = messageParser; + this.messageEncoder = messageEncoder; + this.clock = clock; + } + + Message sendRequestMessage(Transaction txn, PeerSession s, + long timestamp, Author author, @Nullable String message) + throws DbException { + Message m = messageEncoder + .encodeRequestMessage(s.getContactGroupId(), timestamp, + s.getLastLocalMessageId(), author, message); + sendMessage(txn, REQUEST, s.getSessionId(), m, true); + return m; + } + + Message sendAcceptMessage(Transaction txn, PeerSession s, long timestamp, + byte[] ephemeralPublicKey, long acceptTimestamp, + Map transportProperties, + boolean visible) + throws DbException { + Message m = messageEncoder + .encodeAcceptMessage(s.getContactGroupId(), timestamp, + s.getLastLocalMessageId(), s.getSessionId(), + ephemeralPublicKey, acceptTimestamp, + transportProperties); + sendMessage(txn, ACCEPT, s.getSessionId(), m, visible); + 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, visible); + return m; + } + + Message sendAuthMessage(Transaction txn, PeerSession s, long timestamp, + byte[] mac, byte[] signature) throws DbException { + Message m = messageEncoder + .encodeAuthMessage(s.getContactGroupId(), timestamp, + s.getLastLocalMessageId(), s.getSessionId(), mac, + signature); + sendMessage(txn, AUTH, s.getSessionId(), m, false); + return m; + } + + Message sendActivateMessage(Transaction txn, PeerSession s, long timestamp) + throws DbException { + Message m = messageEncoder + .encodeActivateMessage(s.getContactGroupId(), timestamp, + s.getLastLocalMessageId(), s.getSessionId()); + sendMessage(txn, ACTIVATE, s.getSessionId(), m, false); + 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, false); + return m; + } + + private void sendMessage(Transaction txn, MessageType type, + SessionId sessionId, Message m, boolean visibleInConversation) + throws DbException { + BdfDictionary meta = messageEncoder + .encodeMetadata(type, sessionId, m.getTimestamp(), true, true, + visibleInConversation); + try { + clientHelper.addLocalMessage(txn, m, meta, true); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + void markMessageVisibleInUi(Transaction txn, MessageId m) + throws DbException { + BdfDictionary meta = new BdfDictionary(); + messageEncoder.setVisibleInUi(meta, true); + try { + clientHelper.mergeMessageMetadata(txn, m, meta); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + void markRequestUnavailableToAnswer(Transaction txn, MessageId m) + throws DbException { + BdfDictionary meta = new BdfDictionary(); + messageEncoder.setAvailableToAnswer(meta, false); + try { + clientHelper.mergeMessageMetadata(txn, m, meta); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + Map getSessions(Transaction txn, + BdfDictionary query) throws DbException, FormatException { + return clientHelper + .getMessageMetadataAsDictionary(txn, getLocalGroup().getId(), + query); + } + + private Group getLocalGroup() { + return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); + } + + 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/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeProtocolEngine.java new file mode 100644 index 000000000..1efb6919b --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeProtocolEngine.java @@ -0,0 +1,510 @@ +package org.briarproject.briar.introduction2; + +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.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.db.ContactExistsException; +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.identity.LocalAuthor; +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.bramble.api.transport.KeySetId; +import org.briarproject.briar.api.client.MessageTracker; +import org.briarproject.briar.api.client.ProtocolStateException; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.introduction2.IntroductionRequest; +import org.briarproject.briar.api.introduction2.IntroductionResponse; +import org.briarproject.briar.api.introduction2.event.IntroductionRequestReceivedEvent; +import org.briarproject.briar.api.introduction2.event.IntroductionResponseReceivedEvent; +import org.briarproject.briar.api.introduction2.event.IntroductionSucceededEvent; + +import java.security.GeneralSecurityException; +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; +import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_AUTH; +import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_RESPONSES; +import static org.briarproject.briar.introduction2.IntroduceeState.LOCAL_ACCEPTED; +import static org.briarproject.briar.introduction2.IntroduceeState.REMOTE_ACCEPTED; + +@Immutable +@NotNullByDefault +class IntroduceeProtocolEngine + extends AbstractProtocolEngine { + + private final IntroductionCrypto crypto; + private final KeyManager keyManager; + private final TransportPropertyManager transportPropertyManager; + + @Inject + IntroduceeProtocolEngine( + DatabaseComponent db, + ClientHelper clientHelper, + ContactManager contactManager, + ContactGroupFactory contactGroupFactory, + MessageTracker messageTracker, + IdentityManager identityManager, + MessageParser messageParser, + MessageEncoder messageEncoder, + Clock clock, + IntroductionCrypto crypto, + KeyManager keyManager, + TransportPropertyManager transportPropertyManager) { + super(db, clientHelper, contactManager, contactGroupFactory, + messageTracker, identityManager, messageParser, messageEncoder, + clock); + this.crypto = crypto; + this.keyManager = keyManager; + this.transportPropertyManager = transportPropertyManager; + } + + @Override + public IntroduceeSession onRequestAction(Transaction txn, + IntroduceeSession session, @Nullable String message, + long timestamp) { + throw new UnsupportedOperationException(); // Invalid in this role + } + + @Override + public IntroduceeSession onAcceptAction(Transaction txn, + IntroduceeSession session, long timestamp) throws DbException { + switch (session.getState()) { + case AWAIT_RESPONSES: + case REMOTE_ACCEPTED: + return onLocalAccept(txn, session, timestamp); + case START: + case LOCAL_DECLINED: + case LOCAL_ACCEPTED: + case AWAIT_AUTH: + case AWAIT_ACTIVATE: + throw new ProtocolStateException(); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroduceeSession onDeclineAction(Transaction txn, + IntroduceeSession session, long timestamp) throws DbException { + switch (session.getState()) { + case AWAIT_RESPONSES: + case REMOTE_ACCEPTED: + return onLocalDecline(txn, session, timestamp); + case START: + case LOCAL_DECLINED: + case LOCAL_ACCEPTED: + case AWAIT_AUTH: + case AWAIT_ACTIVATE: + throw new ProtocolStateException(); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroduceeSession onRequestMessage(Transaction txn, + IntroduceeSession session, RequestMessage m) + throws DbException, FormatException { + switch (session.getState()) { + case START: + return onRemoteRequest(txn, session, m); + case AWAIT_RESPONSES: + case LOCAL_DECLINED: + case LOCAL_ACCEPTED: + case REMOTE_ACCEPTED: + case AWAIT_AUTH: + case AWAIT_ACTIVATE: + return abort(txn, session); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroduceeSession onAcceptMessage(Transaction txn, + IntroduceeSession session, AcceptMessage m) + throws DbException, FormatException { + switch (session.getState()) { + case AWAIT_RESPONSES: + case LOCAL_ACCEPTED: + return onRemoteAccept(txn, session, m); + case START: + case LOCAL_DECLINED: + case REMOTE_ACCEPTED: + case AWAIT_AUTH: + case AWAIT_ACTIVATE: + return abort(txn, session); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroduceeSession onDeclineMessage(Transaction txn, + IntroduceeSession session, DeclineMessage m) + throws DbException, FormatException { + switch (session.getState()) { + case START: + return session; // Ignore in the START state + case AWAIT_RESPONSES: + case LOCAL_DECLINED: + case LOCAL_ACCEPTED: + return onRemoteDecline(txn, session, m); + case REMOTE_ACCEPTED: + case AWAIT_AUTH: + case AWAIT_ACTIVATE: + return abort(txn, session); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroduceeSession onAuthMessage(Transaction txn, + IntroduceeSession session, AuthMessage m) + throws DbException, FormatException { + switch (session.getState()) { + case AWAIT_AUTH: + return onRemoteAuth(txn, session, m); + case START: + case AWAIT_RESPONSES: + case LOCAL_DECLINED: + case LOCAL_ACCEPTED: + case REMOTE_ACCEPTED: + case AWAIT_ACTIVATE: + return abort(txn, session); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroduceeSession onActivateMessage(Transaction txn, + IntroduceeSession session, ActivateMessage m) + throws DbException, FormatException { + switch (session.getState()) { + case AWAIT_ACTIVATE: + return onRemoteActivate(txn, session, m); + case START: + case AWAIT_RESPONSES: + case LOCAL_DECLINED: + case LOCAL_ACCEPTED: + case REMOTE_ACCEPTED: + case AWAIT_AUTH: + return abort(txn, session); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroduceeSession onAbortMessage(Transaction txn, + IntroduceeSession session, AbortMessage m) + throws DbException, FormatException { + return onRemoteAbort(txn, session, m); + } + + private IntroduceeSession onRemoteRequest(Transaction txn, + IntroduceeSession s, RequestMessage m) throws DbException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getPreviousMessageId())) + return abort(txn, s); + + // Mark the request visible in the UI + markMessageVisibleInUi(txn, m.getMessageId()); + + // Add SessionId to message metadata + addSessionId(txn, m.getMessageId(), s.getSessionId()); + + // Track the incoming message + messageTracker + .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); + + // Broadcast IntroductionRequestReceivedEvent + Contact c = contactManager.getContact(txn, s.getIntroducer().getId(), + identityManager.getLocalAuthor(txn).getId()); + boolean contactExists = false; // TODO + IntroductionRequest request = + new IntroductionRequest(s.getSessionId(), m.getMessageId(), + m.getGroupId(), INTRODUCEE, m.getTimestamp(), false, + false, false, false, m.getAuthor().getName(), false, + m.getMessage(), false, contactExists); + IntroductionRequestReceivedEvent e = + new IntroductionRequestReceivedEvent(c.getId(), request); + txn.attach(e); + + // Move to the AWAIT_RESPONSES state + return IntroduceeSession.addRemoteRequest(s, AWAIT_RESPONSES, m); + } + + private IntroduceeSession onLocalAccept(Transaction txn, + IntroduceeSession s, long timestamp) throws DbException { + // Mark the request message unavailable to answer + MessageId requestId = s.getLastRemoteMessageId(); + if (requestId == null) throw new IllegalStateException(); + markRequestUnavailableToAnswer(txn, requestId); + + // Create ephemeral key pair and get local transport properties + KeyPair keyPair = crypto.generateKeyPair(); + byte[] publicKey = keyPair.getPublic().getEncoded(); + byte[] privateKey = keyPair.getPrivate().getEncoded(); + Map transportProperties = + transportPropertyManager.getLocalProperties(txn); + + // Send a ACCEPT message + long localTimestamp = + Math.max(timestamp, getLocalTimestamp(s)); + Message sent = sendAcceptMessage(txn, s, localTimestamp, publicKey, + localTimestamp, transportProperties, true); + // Track the message + messageTracker.trackOutgoingMessage(txn, sent); + + // Determine the next state + IntroduceeState state = + s.getState() == AWAIT_RESPONSES ? LOCAL_ACCEPTED : AWAIT_AUTH; + IntroduceeSession sNew = IntroduceeSession + .addLocalAccept(s, state, sent, publicKey, privateKey, + localTimestamp, transportProperties); + + if (state == AWAIT_AUTH) { + // Move to the AWAIT_AUTH state + return onLocalAuth(txn, sNew); + } + // Move to the LOCAL_ACCEPTED state + return sNew; + } + + private IntroduceeSession onLocalDecline(Transaction txn, + IntroduceeSession s, long timestamp) throws DbException { + // Mark the request message unavailable to answer + MessageId requestId = s.getLastRemoteMessageId(); + if (requestId == null) throw new IllegalStateException(); + markRequestUnavailableToAnswer(txn, requestId); + + // Send a DECLINE message + long localTimestamp = Math.max(timestamp, getLocalTimestamp(s)); + Message sent = sendDeclineMessage(txn, s, localTimestamp, true); + // Track the message + messageTracker.trackOutgoingMessage(txn, sent); + + // Move to the START state + return IntroduceeSession.clear(s, sent.getId(), sent.getTimestamp(), + s.getLastRemoteMessageId()); + } + + private IntroduceeSession onRemoteAccept(Transaction txn, + IntroduceeSession s, AcceptMessage m) + throws DbException, FormatException { + // The timestamp must be higher than the last request message + if (m.getTimestamp() <= s.getRequestTimestamp()) + return abort(txn, s); + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getPreviousMessageId())) + return abort(txn, s); + + // Broadcast IntroductionResponseReceivedEvent + Contact c = contactManager.getContact(s.getIntroducer().getId(), + identityManager.getLocalAuthor(txn).getId()); + IntroductionResponse request = + new IntroductionResponse(s.getSessionId(), m.getMessageId(), + m.getGroupId(), INTRODUCEE, m.getTimestamp(), false, + false, false, false, s.getRemoteAuthor().getName(), + true); + IntroductionResponseReceivedEvent e = + new IntroductionResponseReceivedEvent(c.getId(), request); + txn.attach(e); + + // Determine next state + IntroduceeState state = + s.getState() == AWAIT_RESPONSES ? REMOTE_ACCEPTED : AWAIT_AUTH; + + if (state == AWAIT_AUTH) { + // Move to the AWAIT_AUTH state and send own auth message + return onLocalAuth(txn, + IntroduceeSession.addRemoteAccept(s, AWAIT_AUTH, m)); + } + // Move to the REMOTE_ACCEPTED state + return IntroduceeSession.addRemoteAccept(s, state, m); + } + + private IntroduceeSession onRemoteDecline(Transaction txn, + IntroduceeSession s, DeclineMessage m) throws DbException { + // The timestamp must be higher than the last request message + if (m.getTimestamp() <= s.getRequestTimestamp()) + return abort(txn, s); + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getPreviousMessageId())) + return abort(txn, s); + + // Move back to START state + return IntroduceeSession + .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), + m.getMessageId()); + } + + private IntroduceeSession onLocalAuth(Transaction txn, IntroduceeSession s) + throws DbException { + boolean alice = isAlice(txn, s); + byte[] mac; + byte[] signature; + SecretKey masterKey; + try { + masterKey = crypto.deriveMasterKey(s, alice); + SecretKey macKey = crypto.deriveMacKey(masterKey, alice); + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + mac = crypto.mac(macKey, s, localAuthor.getId(), alice); + signature = crypto.sign(macKey, localAuthor.getPrivateKey()); + } catch (GeneralSecurityException e) { + // TODO + return abort(txn, s); + } catch (FormatException e) { + throw new AssertionError(e); + } + if (s.getState() != AWAIT_AUTH) throw new AssertionError(); + Message sent = sendAuthMessage(txn, s, getLocalTimestamp(s), mac, + signature); + return IntroduceeSession.addLocalAuth(s, AWAIT_AUTH, masterKey, sent); + } + + private IntroduceeSession onRemoteAuth(Transaction txn, + IntroduceeSession s, AuthMessage m) + throws DbException, FormatException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getPreviousMessageId())) + return abort(txn, s); + + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + try { + crypto.verifyMac(m.getMac(), s, localAuthor.getId()); + crypto.verifySignature(m.getSignature(), s, localAuthor.getId()); + } catch (GeneralSecurityException e) { + return abort(txn, s); + } + + try { + ContactId c = contactManager + .addContact(txn, s.getRemoteAuthor(), localAuthor.getId(), + false, false); + //noinspection ConstantConditions + transportPropertyManager.addRemoteProperties(txn, c, + s.getRemoteTransportProperties()); + } catch (ContactExistsException e) { + // TODO + } + + long timestamp = + Math.min(s.getAcceptTimestamp(), s.getRemoteAcceptTimestamp()); + if (timestamp == -1) throw new AssertionError(); + + //noinspection ConstantConditions + Map keys = keyManager + .addUnboundKeys(txn, new SecretKey(s.getMasterKey()), timestamp, + isAlice(txn, s)); + + Message sent = sendActivateMessage(txn, s, getLocalTimestamp(s)); + + // Move to AWAIT_ACTIVATE state and clear key material from session + return IntroduceeSession.awaitActivate(s, m, sent, keys); + } + + private IntroduceeSession onRemoteActivate(Transaction txn, + IntroduceeSession s, ActivateMessage m) throws DbException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getPreviousMessageId())) + return abort(txn, s); + + Contact c = contactManager.getContact(txn, s.getRemoteAuthor().getId(), + identityManager.getLocalAuthor(txn).getId()); + keyManager.bindKeys(txn, c.getId(), s.getTransportKeys()); + keyManager.activateKeys(txn, s.getTransportKeys()); + + // TODO remove when concept of inactive contacts is removed + contactManager.setContactActive(txn, c.getId(), true); + + // Broadcast IntroductionSucceededEvent + IntroductionSucceededEvent e = new IntroductionSucceededEvent(c); + txn.attach(e); + + // Move back to START state + return IntroduceeSession + .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), + m.getMessageId()); + } + + private IntroduceeSession onRemoteAbort(Transaction txn, + IntroduceeSession s, AbortMessage m) + throws DbException { + // Mark the request message unavailable to answer + MessageId requestId = s.getLastRemoteMessageId(); + if (requestId == null) throw new IllegalStateException(); + markRequestUnavailableToAnswer(txn, requestId); + + // Reset the session back to initial state + return IntroduceeSession + .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), + m.getMessageId()); + } + + private IntroduceeSession abort(Transaction txn, IntroduceeSession s) + throws DbException { + // Mark the request message unavailable to answer + MessageId requestId = s.getLastRemoteMessageId(); + if (requestId == null) throw new IllegalStateException(); + markRequestUnavailableToAnswer(txn, requestId); + + // Send an ABORT message + Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s)); + + // Reset the session back to initial state + return IntroduceeSession.clear(s, sent.getId(), sent.getTimestamp(), + s.getLastRemoteMessageId()); + } + + private boolean isInvalidDependency(IntroduceeSession s, + @Nullable MessageId dependency) { + return isInvalidDependency(s.getLastRemoteMessageId(), dependency); + } + + private long getLocalTimestamp(IntroduceeSession s) { + return getLocalTimestamp(s.getLocalTimestamp(), + s.getRequestTimestamp()); + } + + private boolean isAlice(Transaction txn, IntroduceeSession s) + throws DbException { + Author localAuthor = identityManager.getLocalAuthor(txn); + return crypto.isAlice(localAuthor.getId(), s.getRemoteAuthor().getId()); + } + + 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/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerProtocolEngine.java new file mode 100644 index 000000000..a65f18301 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerProtocolEngine.java @@ -0,0 +1,462 @@ +package org.briarproject.briar.introduction2; + +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.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.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.bramble.api.system.Clock; +import org.briarproject.briar.api.client.MessageTracker; +import org.briarproject.briar.api.client.ProtocolStateException; +import org.briarproject.briar.api.introduction2.IntroductionResponse; +import org.briarproject.briar.api.introduction2.event.IntroductionResponseReceivedEvent; +import org.briarproject.briar.introduction2.IntroducerSession.Introducee; + +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; +import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATES; +import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATE_A; +import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATE_B; +import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTHS; +import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTH_A; +import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTH_B; +import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSES; +import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSE_A; +import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSE_B; +import static org.briarproject.briar.introduction2.IntroducerState.START; + +@Immutable +@NotNullByDefault +class IntroducerProtocolEngine + extends AbstractProtocolEngine { + + @Inject + IntroducerProtocolEngine( + DatabaseComponent db, + ClientHelper clientHelper, + ContactManager contactManager, + ContactGroupFactory contactGroupFactory, + MessageTracker messageTracker, + IdentityManager identityManager, + MessageParser messageParser, + MessageEncoder messageEncoder, + Clock clock) { + super(db, clientHelper, contactManager, contactGroupFactory, + messageTracker, identityManager, messageParser, messageEncoder, + clock); + } + + @Override + public IntroducerSession onRequestAction(Transaction txn, + IntroducerSession s, @Nullable String message, long timestamp) + throws DbException { + switch (s.getState()) { + case START: + return onLocalRequest(txn, s, message, timestamp); + case AWAIT_RESPONSES: + case AWAIT_RESPONSE_A: + case AWAIT_RESPONSE_B: + case AWAIT_AUTHS: + case AWAIT_AUTH_A: + case AWAIT_AUTH_B: + case AWAIT_ACTIVATES: + case AWAIT_ACTIVATE_A: + case AWAIT_ACTIVATE_B: + throw new ProtocolStateException(); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroducerSession onAcceptAction(Transaction txn, + IntroducerSession s, long timestamp) { + throw new UnsupportedOperationException(); // Invalid in this role + } + + @Override + public IntroducerSession onDeclineAction(Transaction txn, + IntroducerSession s, long timestamp) { + throw new UnsupportedOperationException(); // Invalid in this role + } + + @Override + public IntroducerSession onRequestMessage(Transaction txn, + IntroducerSession s, RequestMessage m) + throws DbException, FormatException { + return abort(txn, s); // Invalid in this role + } + + @Override + public IntroducerSession onAcceptMessage(Transaction txn, + IntroducerSession s, AcceptMessage m) + throws DbException, FormatException { + switch (s.getState()) { + case AWAIT_RESPONSES: + case AWAIT_RESPONSE_A: + case AWAIT_RESPONSE_B: + return onRemoteAccept(txn, s, m); + case START: + // TODO check and update lastRemoteMsgId? + return s; // Ignored in this state + case AWAIT_AUTHS: + case AWAIT_AUTH_A: + case AWAIT_AUTH_B: + case AWAIT_ACTIVATES: + case AWAIT_ACTIVATE_A: + case AWAIT_ACTIVATE_B: + return abort(txn, s); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroducerSession onDeclineMessage(Transaction txn, + IntroducerSession s, DeclineMessage m) + throws DbException, FormatException { + switch (s.getState()) { + case AWAIT_RESPONSES: + case AWAIT_RESPONSE_A: + case AWAIT_RESPONSE_B: + return onRemoteDecline(txn, s, m); + case START: + // TODO check and update lastRemoteMsgId? + return s; // Ignored in this state + case AWAIT_AUTHS: + case AWAIT_AUTH_A: + case AWAIT_AUTH_B: + case AWAIT_ACTIVATES: + case AWAIT_ACTIVATE_A: + case AWAIT_ACTIVATE_B: + return abort(txn, s); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroducerSession onAuthMessage(Transaction txn, IntroducerSession s, + AuthMessage m) throws DbException, FormatException { + switch (s.getState()) { + case AWAIT_AUTHS: + case AWAIT_AUTH_A: + case AWAIT_AUTH_B: + return onRemoteAuth(txn, s, m); + case START: + case AWAIT_RESPONSES: + case AWAIT_RESPONSE_A: + case AWAIT_RESPONSE_B: + case AWAIT_ACTIVATES: + case AWAIT_ACTIVATE_A: + case AWAIT_ACTIVATE_B: + return abort(txn, s); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroducerSession onActivateMessage(Transaction txn, + IntroducerSession s, ActivateMessage m) + throws DbException, FormatException { + switch (s.getState()) { + case AWAIT_ACTIVATES: + case AWAIT_ACTIVATE_A: + case AWAIT_ACTIVATE_B: + return onRemoteActivate(txn, s, m); + case START: + case AWAIT_RESPONSES: + case AWAIT_RESPONSE_A: + case AWAIT_RESPONSE_B: + case AWAIT_AUTHS: + case AWAIT_AUTH_A: + case AWAIT_AUTH_B: + return abort(txn, s); // Invalid in these states + default: + throw new AssertionError(); + } + } + + @Override + public IntroducerSession onAbortMessage(Transaction txn, + IntroducerSession s, AbortMessage m) + throws DbException, FormatException { + return onRemoteAbort(txn, s, m); + } + + private IntroducerSession onLocalRequest(Transaction txn, + IntroducerSession s, + @Nullable String message, long timestamp) throws DbException { + // Send REQUEST messages + long localTimestamp = + Math.max(timestamp, getLocalTimestamp(s, s.getIntroducee1())); + Message sent1 = sendRequestMessage(txn, s.getIntroducee1(), + localTimestamp, s.getIntroducee2().author, message + ); + Message sent2 = sendRequestMessage(txn, s.getIntroducee2(), + localTimestamp, s.getIntroducee1().author, message + ); + // Track the messages + messageTracker.trackOutgoingMessage(txn, sent1); + messageTracker.trackOutgoingMessage(txn, sent2); + // Move to the AWAIT_RESPONSES state + Introducee introducee1 = new Introducee(s.getIntroducee1(), sent1); + Introducee introducee2 = new Introducee(s.getIntroducee2(), sent2); + return new IntroducerSession(s.getSessionId(), AWAIT_RESPONSES, + localTimestamp, introducee1, introducee2); + } + + private IntroducerSession onRemoteAccept(Transaction txn, + IntroducerSession s, AcceptMessage m) + throws DbException, FormatException { + // The timestamp must be higher than the last request message + if (m.getTimestamp() <= s.getRequestTimestamp()) + return abort(txn, s); + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) + return abort(txn, s); + + // Mark the response visible in the UI + markMessageVisibleInUi(txn, m.getMessageId()); + // Track the incoming message + messageTracker + .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); + + // Forward ACCEPT message + Introducee i = getOtherIntroducee(s, m.getGroupId()); + long timestamp = getLocalTimestamp(s, i); + Message sent = + sendAcceptMessage(txn, i, timestamp, m.getEphemeralPublicKey(), + m.getAcceptTimestamp(), m.getTransportProperties(), + false); + + // Move to the next state + IntroducerState state = AWAIT_AUTHS; + Introducee introducee1, introducee2; + Contact c; + if (i.equals(s.getIntroducee1())) { + if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_A; + introducee1 = new Introducee(s.getIntroducee1(), sent); + introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); + c = contactManager.getContact(s.getIntroducee2().author.getId(), + identityManager.getLocalAuthor(txn).getId()); + } else if (i.equals(s.getIntroducee2())) { + if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_B; + introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); + introducee2 = new Introducee(s.getIntroducee2(), sent); + c = contactManager.getContact(s.getIntroducee1().author.getId(), + identityManager.getLocalAuthor(txn).getId()); + } else throw new AssertionError(); + + // Broadcast IntroductionResponseReceivedEvent + IntroductionResponse request = + new IntroductionResponse(s.getSessionId(), m.getMessageId(), + m.getGroupId(), INTRODUCER, m.getTimestamp(), false, + false, false, false, c.getAuthor().getName(), true); + IntroductionResponseReceivedEvent e = + new IntroductionResponseReceivedEvent(c.getId(), request); + txn.attach(e); + + return new IntroducerSession(s.getSessionId(), state, + s.getRequestTimestamp(), introducee1, introducee2); + } + + private IntroducerSession onRemoteDecline(Transaction txn, + IntroducerSession s, DeclineMessage m) + throws DbException, FormatException { + // The timestamp must be higher than the last request message + if (m.getTimestamp() <= s.getRequestTimestamp()) + return abort(txn, s); + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) + return abort(txn, s); + + // Mark the response visible in the UI + markMessageVisibleInUi(txn, m.getMessageId()); + // Track the incoming message + messageTracker + .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); + + // Forward DECLINE message + Introducee i = getOtherIntroducee(s, m.getGroupId()); + long timestamp = getLocalTimestamp(s, i); + Message sent = sendDeclineMessage(txn, i, timestamp, false); + // Track the message + messageTracker.trackOutgoingMessage(txn, sent); + + // Move to the START state + Introducee introducee1, introducee2; + Contact c; + if (i.equals(s.getIntroducee1())) { + introducee1 = new Introducee(s.getIntroducee1(), sent); + introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); + c = contactManager.getContact(s.getIntroducee2().author.getId(), + identityManager.getLocalAuthor(txn).getId()); + } else if (i.equals(s.getIntroducee2())) { + introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); + introducee2 = new Introducee(s.getIntroducee2(), sent); + c = contactManager.getContact(s.getIntroducee2().author.getId(), + identityManager.getLocalAuthor(txn).getId()); + } else throw new AssertionError(); + + // Broadcast IntroductionResponseReceivedEvent + IntroductionResponse request = + new IntroductionResponse(s.getSessionId(), m.getMessageId(), + m.getGroupId(), INTRODUCER, m.getTimestamp(), false, + false, false, false, c.getAuthor().getName(), false); + IntroductionResponseReceivedEvent e = + new IntroductionResponseReceivedEvent(c.getId(), request); + txn.attach(e); + + return new IntroducerSession(s.getSessionId(), START, + s.getRequestTimestamp(), introducee1, introducee2); + } + + private IntroducerSession onRemoteAuth(Transaction txn, + IntroducerSession s, AuthMessage m) + throws DbException, FormatException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) + return abort(txn, s); + + // Forward AUTH message + Introducee i = getOtherIntroducee(s, m.getGroupId()); + long timestamp = getLocalTimestamp(s, i); + Message sent = sendAuthMessage(txn, i, timestamp, m.getMac(), + m.getSignature()); + + // Move to the next state + IntroducerState state = AWAIT_ACTIVATES; + Introducee introducee1, introducee2; + if (i.equals(s.getIntroducee1())) { + if (s.getState() == AWAIT_AUTHS) state = AWAIT_AUTH_A; + introducee1 = new Introducee(s.getIntroducee1(), sent); + introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); + } else if (i.equals(s.getIntroducee2())) { + if (s.getState() == AWAIT_AUTHS) state = AWAIT_AUTH_B; + introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); + introducee2 = new Introducee(s.getIntroducee2(), sent); + } else throw new AssertionError(); + return new IntroducerSession(s.getSessionId(), state, + s.getRequestTimestamp(), introducee1, introducee2); + } + + private IntroducerSession onRemoteActivate(Transaction txn, + IntroducerSession s, ActivateMessage m) + throws DbException, FormatException { + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) + return abort(txn, s); + + // Forward AUTH message + Introducee i = getOtherIntroducee(s, m.getGroupId()); + long timestamp = getLocalTimestamp(s, i); + Message sent = sendActivateMessage(txn, i, timestamp); + + // Move to the next state + IntroducerState state = START; + Introducee introducee1, introducee2; + if (i.equals(s.getIntroducee1())) { + if (s.getState() == AWAIT_ACTIVATES) state = AWAIT_ACTIVATE_A; + introducee1 = new Introducee(s.getIntroducee1(), sent); + introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); + } else if (i.equals(s.getIntroducee2())) { + if (s.getState() == AWAIT_ACTIVATES) state = AWAIT_ACTIVATE_B; + introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); + introducee2 = new Introducee(s.getIntroducee2(), sent); + } else throw new AssertionError(); + return new IntroducerSession(s.getSessionId(), state, + s.getRequestTimestamp(), introducee1, introducee2); + } + + private IntroducerSession onRemoteAbort(Transaction txn, + IntroducerSession s, AbortMessage m) + throws DbException, FormatException { + // Mark any REQUEST messages in the session unavailable to answer + markRequestsUnavailableToAnswer(txn, s); + + // Forward ABORT message + Introducee i = getOtherIntroducee(s, m.getGroupId()); + long timestamp = getLocalTimestamp(s, i); + Message sent = sendAbortMessage(txn, i, timestamp); + + // Reset the session back to initial state + Introducee introducee1, introducee2; + if (i.equals(s.getIntroducee1())) { + introducee1 = new Introducee(s.getIntroducee1(), sent); + introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); + } else if (i.equals(s.getIntroducee2())) { + introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); + introducee2 = new Introducee(s.getIntroducee2(), sent); + } else throw new AssertionError(); + return new IntroducerSession(s.getSessionId(), START, + s.getRequestTimestamp(), introducee1, introducee2); + } + + private IntroducerSession abort(Transaction txn, + IntroducerSession s) throws DbException, FormatException { + // Mark any REQUEST messages in the session unavailable to answer + markRequestsUnavailableToAnswer(txn, s); + // Send an ABORT message to both introducees + long timestamp1 = getLocalTimestamp(s, s.getIntroducee1()); + Message sent1 = sendAbortMessage(txn, s.getIntroducee1(), timestamp1); + long timestamp2 = getLocalTimestamp(s, s.getIntroducee2()); + Message sent2 = sendAbortMessage(txn, s.getIntroducee2(), timestamp2); + // Reset the session back to initial state + Introducee introducee1 = new Introducee(s.getIntroducee1(), sent1); + Introducee introducee2 = new Introducee(s.getIntroducee2(), sent2); + return new IntroducerSession(s.getSessionId(), START, + s.getRequestTimestamp(), introducee1, introducee2); + } + + private void markRequestsUnavailableToAnswer(Transaction txn, Session s) + throws DbException, FormatException { + BdfDictionary query = messageParser + .getInvitesAvailableToAnswerQuery(s.getSessionId()); + Map results = getSessions(txn, query); + for (MessageId m : results.keySet()) + markRequestUnavailableToAnswer(txn, m); + } + + private Introducee getIntroducee(IntroducerSession s, GroupId g) { + if (s.getIntroducee1().groupId.equals(g)) return s.getIntroducee1(); + else if (s.getIntroducee2().groupId.equals(g)) + return s.getIntroducee2(); + else throw new AssertionError(); + } + + private Introducee getOtherIntroducee(IntroducerSession s, GroupId g) { + if (s.getIntroducee1().groupId.equals(g)) return s.getIntroducee2(); + else if (s.getIntroducee2().groupId.equals(g)) + return s.getIntroducee1(); + else throw new AssertionError(); + } + + private boolean isInvalidDependency(IntroducerSession session, + GroupId contactGroupId, @Nullable MessageId dependency) { + MessageId expected = + getIntroducee(session, contactGroupId).lastRemoteMessageId; + return isInvalidDependency(expected, dependency); + } + + private long getLocalTimestamp(IntroducerSession s, PeerSession p) { + return getLocalTimestamp(p.getLocalTimestamp(), + s.getRequestTimestamp()); + } + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java index ea5734468..634c0fff1 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java @@ -2,6 +2,9 @@ package org.briarproject.briar.introduction2; 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"; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionManagerImpl.java new file mode 100644 index 000000000..55a6e552a --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionManagerImpl.java @@ -0,0 +1,459 @@ +package org.briarproject.briar.introduction2; + +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.ContactHook; +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.identity.LocalAuthor; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +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.sync.MessageStatus; +import org.briarproject.briar.api.client.MessageTracker; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.introduction2.IntroductionManager; +import org.briarproject.briar.api.introduction2.IntroductionMessage; +import org.briarproject.briar.api.introduction2.IntroductionRequest; +import org.briarproject.briar.api.introduction2.IntroductionResponse; +import org.briarproject.briar.api.introduction2.Role; +import org.briarproject.briar.client.ConversationClientImpl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; +import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; +import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; +import static org.briarproject.briar.introduction2.IntroductionConstants.GROUP_KEY_CONTACT_ID; +import static org.briarproject.briar.introduction2.MessageType.ABORT; +import static org.briarproject.briar.introduction2.MessageType.ACCEPT; +import static org.briarproject.briar.introduction2.MessageType.ACTIVATE; +import static org.briarproject.briar.introduction2.MessageType.AUTH; +import static org.briarproject.briar.introduction2.MessageType.DECLINE; +import static org.briarproject.briar.introduction2.MessageType.REQUEST; + +@Immutable +@NotNullByDefault +class IntroductionManagerImpl extends ConversationClientImpl + implements IntroductionManager, Client, ContactHook { + + private final ContactGroupFactory contactGroupFactory; + private final MessageParser messageParser; + private final SessionEncoder sessionEncoder; + private final SessionParser sessionParser; + private final IntroducerProtocolEngine introducerEngine; + private final IntroduceeProtocolEngine introduceeEngine; + private final IntroductionCrypto crypto; + private final IdentityManager identityManager; + + @Inject + IntroductionManagerImpl( + DatabaseComponent db, + ClientHelper clientHelper, + MetadataParser metadataParser, + MessageTracker messageTracker, + ContactGroupFactory contactGroupFactory, + MessageParser messageParser, + SessionEncoder sessionEncoder, + SessionParser sessionParser, + IntroducerProtocolEngine introducerEngine, + IntroduceeProtocolEngine introduceeEngine, + IntroductionCrypto crypto, + IdentityManager identityManager) { + super(db, clientHelper, metadataParser, messageTracker); + this.contactGroupFactory = contactGroupFactory; + this.messageParser = messageParser; + this.sessionEncoder = sessionEncoder; + this.sessionParser = sessionParser; + this.introducerEngine = introducerEngine; + this.introduceeEngine = introduceeEngine; + this.crypto = crypto; + this.identityManager = identityManager; + } + + @Override + public void createLocalState(Transaction txn) throws DbException { + // Create a local group to store protocol sessions + Group localGroup = getLocalGroup(); + 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.getContacts(txn)) addingContact(txn, c); + } + + @Override + // TODO adapt to use upcoming ClientVersioning client + public void addingContact(Transaction txn, Contact c) throws DbException { + // Create a group to share with the contact + Group g = getContactGroup(c); + // Return if we've already set things up for this contact + if (db.containsGroup(txn, g.getId())) return; + // Store the group and share it with the contact + db.addGroup(txn, g); + db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); + // 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); + } + } + + @Override + public void removingContact(Transaction txn, Contact c) throws DbException { + // Remove the contact group (all messages will be removed with it) + db.removeGroup(txn, getContactGroup(c)); + // TODO abort other sessions the contact is involved in + } + + @Override + public Group getContactGroup(Contact c) { + return contactGroupFactory + .createContactGroup(CLIENT_ID, CLIENT_VERSION, c); + } + + @Override + protected boolean incomingMessage(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 newIntroduceeSession = null; + if (sessionId == null) { + if (meta.getMessageType() != REQUEST) throw new AssertionError(); + newIntroduceeSession = createNewIntroduceeSession(txn, m, body); + sessionId = newIntroduceeSession.getSessionId(); + } + StoredSession ss = getSession(txn, sessionId); + // Handle the message + Session session; + MessageId storageId; + if (ss == null) { + if (meta.getMessageType() != REQUEST) throw new FormatException(); + if (newIntroduceeSession == null) throw new AssertionError(); + storageId = createStorageId(txn); + session = handleMessage(txn, m, body, meta.getMessageType(), + newIntroduceeSession, introduceeEngine); + } else { + storageId = ss.storageId; + Role role = sessionParser.getRole(ss.bdfSession); + if (role == INTRODUCER) { + session = handleMessage(txn, m, body, meta.getMessageType(), + sessionParser.parseIntroducerSession(ss.bdfSession), + introducerEngine); + } else if (role == INTRODUCEE) { + session = handleMessage(txn, m, body, meta.getMessageType(), + sessionParser.parseIntroduceeSession(m.getGroupId(), + ss.bdfSession), introduceeEngine); + } else throw new AssertionError(); + } + // Store the updated session + storeSession(txn, storageId, session); + return false; + } + + private IntroduceeSession createNewIntroduceeSession(Transaction txn, + Message m, BdfList body) throws DbException, FormatException { + ContactId introducerId = getContactId(txn, m.getGroupId()); + Author introducer = db.getContact(txn, introducerId).getAuthor(); + Author alice = identityManager.getLocalAuthor(txn); + Author bob = messageParser.parseRequestMessage(m, body).getAuthor(); + SessionId sessionId = crypto.getSessionId(introducer, alice, bob); + return IntroduceeSession + .getInitial(m.getGroupId(), sessionId, introducer, bob); + } + + private S handleMessage(Transaction txn, Message m, + BdfList body, MessageType type, S session, ProtocolEngine engine) + throws DbException, FormatException { + if (type == REQUEST) { + RequestMessage request = messageParser.parseRequestMessage(m, body); + return engine.onRequestMessage(txn, session, request); + } else if (type == ACCEPT) { + AcceptMessage accept = messageParser.parseAcceptMessage(m, body); + return engine.onAcceptMessage(txn, session, accept); + } else if (type == DECLINE) { + DeclineMessage decline = messageParser.parseDeclineMessage(m, body); + return engine.onDeclineMessage(txn, session, decline); + } else if (type == AUTH) { + AuthMessage auth = messageParser.parseAuthMessage(m, body); + return engine.onAuthMessage(txn, session, auth); + } else if (type == ACTIVATE) { + ActivateMessage activate = + messageParser.parseActivateMessage(m, body); + return engine.onActivateMessage(txn, session, activate); + } else if (type == ABORT) { + AbortMessage abort = messageParser.parseAbortMessage(m, body); + return engine.onAbortMessage(txn, session, abort); + } else { + throw new AssertionError(); + } + } + + @Nullable + private StoredSession getSession(Transaction txn, + @Nullable SessionId sessionId) throws DbException, FormatException { + if (sessionId == null) return null; + BdfDictionary query = sessionParser.getSessionQuery(sessionId); + Map results = clientHelper + .getMessageMetadataAsDictionary(txn, getLocalGroup().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()); + } + + 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()); + } + + private MessageId createStorageId(Transaction txn) throws DbException { + Message m = clientHelper + .createMessageForStoringMetadata(getLocalGroup().getId()); + db.addLocalMessage(txn, m, new Metadata(), false); + return m.getId(); + } + + private void storeSession(Transaction txn, MessageId storageId, + Session session) throws DbException, FormatException { + BdfDictionary d; + if (session.getRole() == INTRODUCER) { + d = sessionEncoder + .encodeIntroducerSession((IntroducerSession) session); + } else if (session.getRole() == INTRODUCEE) { + d = sessionEncoder + .encodeIntroduceeSession((IntroduceeSession) session); + } else { + throw new AssertionError(); + } + clientHelper.mergeMessageMetadata(txn, storageId, d); + } + + @Override + public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg, + 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, c1.getAuthor(), + c2.getAuthor()); + StoredSession ss = getSession(txn, sessionId); + // Create or parse the session + IntroducerSession session; + MessageId storageId; + if (ss == null) { + // This is the first request - create a new session + GroupId groupId1 = getContactGroup(c1).getId(); + GroupId groupId2 = getContactGroup(c2).getId(); + session = new IntroducerSession(sessionId, groupId1, + c1.getAuthor(), groupId2, c2.getAuthor()); + storageId = createStorageId(txn); + } else { + // An earlier request exists, so we already have a session + session = sessionParser.parseIntroducerSession(ss.bdfSession); + storageId = ss.storageId; + } + // Handle the request action + session = introducerEngine + .onRequestAction(txn, session, msg, timestamp); + // Store the updated session + storeSession(txn, storageId, session); + db.commitTransaction(txn); + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + } + + @Override + public void acceptIntroduction(ContactId contactId, SessionId sessionId, + long timestamp) throws DbException { + respondToRequest(contactId, sessionId, timestamp, true); + } + + @Override + public void declineIntroduction(ContactId contactId, SessionId sessionId, + long timestamp) throws DbException { + respondToRequest(contactId, sessionId, timestamp, false); + } + + private void respondToRequest(ContactId contactId, SessionId sessionId, + long timestamp, boolean accept) throws DbException { + Transaction txn = db.startTransaction(false); + try { + // Look up the session + StoredSession ss = getSession(txn, sessionId); + if (ss == null) throw new IllegalArgumentException(); + // Parse the session + Contact contact = db.getContact(txn, contactId); + GroupId contactGroupId = getContactGroup(contact).getId(); + IntroduceeSession session = sessionParser + .parseIntroduceeSession(contactGroupId, ss.bdfSession); + // Handle the join or leave action + if (accept) { + session = introduceeEngine + .onAcceptAction(txn, session, timestamp); + } else { + session = introduceeEngine + .onDeclineAction(txn, session, timestamp); + } + // Store the updated session + storeSession(txn, ss.storageId, session); + db.commitTransaction(txn); + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + } + + @Override + public Collection getIntroductionMessages(ContactId c) + throws DbException { + List messages; + Transaction txn = db.startTransaction(true); + try { + Contact contact = db.getContact(txn, c); + GroupId contactGroupId = getContactGroup(contact).getId(); + BdfDictionary query = messageParser.getMessagesVisibleInUiQuery(); + Map results = clientHelper + .getMessageMetadataAsDictionary(txn, contactGroupId, query); + messages = new ArrayList<>(results.size()); + for (Map.Entry e : results.entrySet()) { + MessageId m = e.getKey(); + MessageMetadata meta = + messageParser.parseMetadata(e.getValue()); + MessageStatus status = db.getMessageStatus(txn, c, m); + StoredSession ss = getSession(txn, meta.getSessionId()); + if (ss == null) throw new AssertionError(); + MessageType type = meta.getMessageType(); + if (type == REQUEST) { + messages.add( + parseInvitationRequest(txn, contactGroupId, m, + meta, status, ss.bdfSession)); + } else if (type == ACCEPT) { + messages.add( + parseInvitationResponse(txn, contactGroupId, m, + meta, status, ss.bdfSession, true)); + } else if (type == DECLINE) { + messages.add( + parseInvitationResponse(txn, contactGroupId, m, + meta, status, ss.bdfSession, false)); + } + } + db.commitTransaction(txn); + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + return messages; + } + + private IntroductionRequest parseInvitationRequest(Transaction txn, + GroupId contactGroupId, MessageId m, MessageMetadata meta, + MessageStatus status, BdfDictionary bdfSession) + throws DbException, FormatException { + Role role = sessionParser.getRole(bdfSession); + SessionId sessionId; + Author author; + if (role == INTRODUCER) { + IntroducerSession session = + sessionParser.parseIntroducerSession(bdfSession); + sessionId = session.getSessionId(); + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + if (localAuthor.equals(session.getIntroducee1().author)) { + author = session.getIntroducee2().author; + } else { + author = session.getIntroducee1().author; + } + } else if (role == INTRODUCEE) { + IntroduceeSession session = sessionParser + .parseIntroduceeSession(contactGroupId, bdfSession); + sessionId = session.getSessionId(); + author = session.getRemoteAuthor(); + } else throw new AssertionError(); + String message = ""; // TODO + boolean contactExists = false; // TODO + + return new IntroductionRequest(sessionId, m, contactGroupId, + role, meta.getTimestamp(), meta.isLocal(), + status.isSent(), status.isSeen(), meta.isRead(), + author.getName(), false, message, !meta.isAvailableToAnswer(), + contactExists); + } + + private IntroductionResponse parseInvitationResponse(Transaction txn, + GroupId contactGroupId, MessageId m, MessageMetadata meta, + MessageStatus status, BdfDictionary bdfSession, boolean accept) + throws FormatException, DbException { + Role role = sessionParser.getRole(bdfSession); + SessionId sessionId; + Author author; + if (role == INTRODUCER) { + IntroducerSession session = + sessionParser.parseIntroducerSession(bdfSession); + sessionId = session.getSessionId(); + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + if (localAuthor.equals(session.getIntroducee1().author)) { + author = session.getIntroducee2().author; + } else { + author = session.getIntroducee1().author; + } + } else if (role == INTRODUCEE) { + IntroduceeSession session = sessionParser + .parseIntroduceeSession(contactGroupId, bdfSession); + sessionId = session.getSessionId(); + author = session.getRemoteAuthor(); + } else throw new AssertionError(); + return new IntroductionResponse(sessionId, m, contactGroupId, + role, meta.getTimestamp(), meta.isLocal(), status.isSent(), + status.isSeen(), meta.isRead(), author.getName(), accept); + } + + private Group getLocalGroup() { + return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); + } + + 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/briar-core/src/main/java/org/briarproject/briar/introduction2/ProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/ProtocolEngine.java new file mode 100644 index 000000000..083296ca0 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/ProtocolEngine.java @@ -0,0 +1,40 @@ +package org.briarproject.briar.introduction2; + +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; + +import javax.annotation.Nullable; + +@NotNullByDefault +interface ProtocolEngine { + + S onRequestAction(Transaction txn, S session, @Nullable String message, + 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 onAcceptMessage(Transaction txn, S session, AcceptMessage m) + throws DbException, FormatException; + + S onDeclineMessage(Transaction txn, S session, DeclineMessage m) + throws DbException, FormatException; + + S onAuthMessage(Transaction txn, S session, AuthMessage m) + throws DbException, FormatException; + + S onActivateMessage(Transaction txn, S session, ActivateMessage m) + throws DbException, FormatException; + + S onAbortMessage(Transaction txn, S session, AbortMessage m) + throws DbException, FormatException; + +} From f81ef30b47e8bfe1e0c05eb51c8a3c01864dee25 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 23 Apr 2018 17:11:10 -0300 Subject: [PATCH 08/21] Replace old introduction client with new one --- .../IntroductionMessageFragment.java | 7 +- .../api/introduction/IntroduceeAction.java | 53 -- .../introduction/IntroduceeProtocolState.java | 82 -- .../api/introduction/IntroducerAction.java | 54 -- .../introduction/IntroducerProtocolState.java | 108 --- .../introduction/IntroductionConstants.java | 120 +-- .../api/introduction/IntroductionManager.java | 9 +- .../api/introduction/IntroductionMessage.java | 8 +- .../api/introduction/IntroductionRequest.java | 17 +- .../introduction/IntroductionResponse.java | 12 +- .../{introduction2 => introduction}/Role.java | 2 +- .../event/IntroductionAbortedEvent.java | 10 +- .../event/IntroductionSucceededEvent.java | 1 + .../introduction2/IntroductionConstants.java | 29 - .../introduction2/IntroductionManager.java | 52 -- .../introduction2/IntroductionMessage.java | 43 - .../introduction2/IntroductionRequest.java | 44 - .../introduction2/IntroductionResponse.java | 35 - .../event/IntroductionAbortedEvent.java | 32 - .../IntroductionRequestReceivedEvent.java | 32 - .../IntroductionResponseReceivedEvent.java | 31 - .../event/IntroductionSucceededEvent.java | 23 - .../AbortMessage.java | 4 +- .../AbstractIntroductionMessage.java} | 6 +- .../AbstractProtocolEngine.java | 18 +- .../AcceptMessage.java | 4 +- .../ActivateMessage.java | 4 +- .../AuthMessage.java | 4 +- .../DeclineMessage.java | 4 +- .../briar/introduction/IntroduceeEngine.java | 372 --------- .../briar/introduction/IntroduceeManager.java | 569 ------------- .../IntroduceeProtocolEngine.java | 80 +- .../IntroduceeSession.java | 10 +- .../IntroduceeState.java | 2 +- .../briar/introduction/IntroducerEngine.java | 370 --------- .../briar/introduction/IntroducerManager.java | 181 ---- .../IntroducerProtocolEngine.java | 121 ++- .../IntroducerSession.java | 6 +- .../IntroducerState.java | 2 +- .../IntroductionConstants.java | 2 +- .../IntroductionCrypto.java | 2 +- .../IntroductionCryptoImpl.java | 18 +- .../IntroductionGroupFactory.java | 33 - .../introduction/IntroductionManagerImpl.java | 785 +++++++++--------- .../introduction/IntroductionModule.java | 52 +- .../introduction/IntroductionValidator.java | 261 +++--- .../MessageEncoder.java | 2 +- .../MessageEncoderImpl.java | 28 +- .../MessageMetadata.java | 2 +- .../MessageParser.java | 2 +- .../MessageParserImpl.java | 18 +- .../briar/introduction/MessageSender.java | 125 --- .../MessageType.java | 2 +- .../PeerSession.java | 2 +- .../ProtocolEngine.java | 2 +- .../RequestMessage.java | 4 +- .../Session.java | 4 +- .../SessionEncoder.java | 7 +- .../SessionEncoderImpl.java | 69 +- .../SessionParser.java | 4 +- .../SessionParserImpl.java | 54 +- .../briar/introduction/State.java | 7 + .../IntroductionManagerImpl.java | 459 ---------- .../introduction2/IntroductionModule.java | 39 - .../introduction2/IntroductionValidator.java | 168 ---- .../briar/introduction2/State.java | 7 - .../introduction/IntroduceeManagerTest.java | 424 ---------- .../introduction/IntroducerManagerTest.java | 179 ---- .../IntroductionCryptoImplTest.java | 2 +- .../IntroductionCryptoTest.java | 4 +- .../IntroductionIntegrationTest.java | 502 ++++------- .../IntroductionIntegrationTestComponent.java | 3 +- .../IntroductionManagerImplTest.java | 291 ------- .../IntroductionValidatorTest.java | 683 ++++++++------- .../MessageEncoderParserIntegrationTest.java | 11 +- .../MessageEncoderTest.java | 9 +- .../briar/introduction/MessageSenderTest.java | 98 --- .../SessionEncoderParserIntegrationTest.java | 14 +- .../IntroductionValidatorTest.java | 428 ---------- .../test/BriarIntegrationTestComponent.java | 6 +- 80 files changed, 1449 insertions(+), 5924 deletions(-) delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroduceeAction.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroduceeProtocolState.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroducerAction.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroducerProtocolState.java rename briar-api/src/main/java/org/briarproject/briar/api/{introduction2 => introduction}/Role.java (91%) delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionManager.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionMessage.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionRequest.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionResponse.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionAbortedEvent.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionRequestReceivedEvent.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionResponseReceivedEvent.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionSucceededEvent.java rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/AbortMessage.java (86%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2/IntroductionMessage.java => introduction/AbstractIntroductionMessage.java} (84%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/AbstractProtocolEngine.java (90%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/AcceptMessage.java (93%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/ActivateMessage.java (85%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/AuthMessage.java (89%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/DeclineMessage.java (86%) delete mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeEngine.java delete mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeManager.java rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/IntroduceeProtocolEngine.java (89%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/IntroduceeSession.java (95%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/IntroduceeState.java (93%) delete mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerEngine.java delete mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerManager.java rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/IntroducerProtocolEngine.java (78%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/IntroducerSession.java (94%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/IntroducerState.java (94%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/IntroductionConstants.java (97%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/IntroductionCrypto.java (98%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/IntroductionCryptoImpl.java (89%) delete mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionGroupFactory.java rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/MessageEncoder.java (97%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/MessageEncoderImpl.java (83%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/MessageMetadata.java (96%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/MessageParser.java (95%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/MessageParserImpl.java (86%) delete mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction/MessageSender.java rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/MessageType.java (92%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/PeerSession.java (91%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/ProtocolEngine.java (96%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/RequestMessage.java (88%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/Session.java (87%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/SessionEncoder.java (57%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/SessionEncoderImpl.java (54%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/SessionParser.java (86%) rename briar-core/src/main/java/org/briarproject/briar/{introduction2 => introduction}/SessionParserImpl.java (69%) create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction/State.java delete mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionManagerImpl.java delete mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionModule.java delete mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java delete mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/State.java delete mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java delete mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction/IntroducerManagerTest.java rename briar-core/src/test/java/org/briarproject/briar/{introduction2 => introduction}/IntroductionCryptoImplTest.java (99%) rename briar-core/src/test/java/org/briarproject/briar/{introduction2 => introduction}/IntroductionCryptoTest.java (91%) delete mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionManagerImplTest.java rename briar-core/src/test/java/org/briarproject/briar/{introduction2 => introduction}/MessageEncoderParserIntegrationTest.java (96%) rename briar-core/src/test/java/org/briarproject/briar/{introduction2 => introduction}/MessageEncoderTest.java (90%) delete mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction/MessageSenderTest.java rename briar-core/src/test/java/org/briarproject/briar/{introduction2 => introduction}/SessionEncoderParserIntegrationTest.java (95%) delete mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java index 3a76421b3..a052e7393 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java @@ -11,7 +11,6 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; @@ -35,7 +34,7 @@ import static android.app.Activity.RESULT_OK; import static android.view.View.GONE; import static android.widget.Toast.LENGTH_SHORT; import static java.util.logging.Level.WARNING; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; +import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; public class IntroductionMessageFragment extends BaseFragment implements TextInputListener { @@ -175,7 +174,7 @@ public class IntroductionMessageFragment extends BaseFragment ui.message.setSendButtonEnabled(false); String msg = ui.message.getText().toString(); - msg = StringUtils.truncateUtf8(msg, MAX_INTRODUCTION_MESSAGE_LENGTH); + msg = StringUtils.truncateUtf8(msg, MAX_REQUEST_MESSAGE_LENGTH); makeIntroduction(contact1, contact2, msg); // don't wait for the introduction to be made before finishing activity @@ -190,7 +189,7 @@ public class IntroductionMessageFragment extends BaseFragment try { long timestamp = System.currentTimeMillis(); introductionManager.makeIntroduction(c1, c2, msg, timestamp); - } catch (DbException | FormatException e) { + } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); introductionError(); } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroduceeAction.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroduceeAction.java deleted file mode 100644 index 68881b9dc..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroduceeAction.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.briarproject.briar.api.introduction; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import javax.annotation.Nullable; - -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; - -@NotNullByDefault -public enum IntroduceeAction { - - LOCAL_ACCEPT, - LOCAL_DECLINE, - LOCAL_ABORT, - REMOTE_REQUEST, - REMOTE_ACCEPT, - REMOTE_DECLINE, - REMOTE_ABORT, - ACK; - - @Nullable - public static IntroduceeAction getRemote(int type, boolean accept) { - if (type == TYPE_REQUEST) return REMOTE_REQUEST; - if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT; - if (type == TYPE_RESPONSE) return REMOTE_DECLINE; - if (type == TYPE_ACK) return ACK; - if (type == TYPE_ABORT) return REMOTE_ABORT; - return null; - } - - @Nullable - public static IntroduceeAction getRemote(int type) { - return getRemote(type, true); - } - - @Nullable - public static IntroduceeAction getLocal(int type, boolean accept) { - if (type == TYPE_RESPONSE && accept) return LOCAL_ACCEPT; - if (type == TYPE_RESPONSE) return LOCAL_DECLINE; - if (type == TYPE_ACK) return ACK; - if (type == TYPE_ABORT) return LOCAL_ABORT; - return null; - } - - @Nullable - public static IntroduceeAction getLocal(int type) { - return getLocal(type, true); - } - -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroduceeProtocolState.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroduceeProtocolState.java deleted file mode 100644 index e696181a0..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroduceeProtocolState.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.briarproject.briar.api.introduction; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import javax.annotation.concurrent.Immutable; - -import static org.briarproject.briar.api.introduction.IntroduceeAction.ACK; -import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_ACCEPT; -import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_DECLINE; -import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_ACCEPT; -import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_DECLINE; -import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_REQUEST; - -@Immutable -@NotNullByDefault -public enum IntroduceeProtocolState { - - ERROR(0), - AWAIT_REQUEST(1) { - @Override - public IntroduceeProtocolState next(IntroduceeAction a) { - if (a == REMOTE_REQUEST) return AWAIT_RESPONSES; - return ERROR; - } - }, - AWAIT_RESPONSES(2) { - @Override - public IntroduceeProtocolState next(IntroduceeAction a) { - if (a == REMOTE_ACCEPT) return AWAIT_LOCAL_RESPONSE; - if (a == REMOTE_DECLINE) return FINISHED; - if (a == LOCAL_ACCEPT) return AWAIT_REMOTE_RESPONSE; - if (a == LOCAL_DECLINE) return FINISHED; - return ERROR; - } - }, - AWAIT_REMOTE_RESPONSE(3) { - @Override - public IntroduceeProtocolState next(IntroduceeAction a) { - if (a == REMOTE_ACCEPT) return AWAIT_ACK; - if (a == REMOTE_DECLINE) return FINISHED; - return ERROR; - } - }, - AWAIT_LOCAL_RESPONSE(4) { - @Override - public IntroduceeProtocolState next(IntroduceeAction a) { - if (a == LOCAL_ACCEPT) return AWAIT_ACK; - if (a == LOCAL_DECLINE) return FINISHED; - return ERROR; - } - }, - AWAIT_ACK(5) { - @Override - public IntroduceeProtocolState next(IntroduceeAction a) { - if (a == ACK) return FINISHED; - return ERROR; - } - }, - FINISHED(6); - - private final int value; - - IntroduceeProtocolState(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - - public static IntroduceeProtocolState fromValue(int value) { - for (IntroduceeProtocolState s : values()) { - if (s.value == value) return s; - } - throw new IllegalArgumentException(); - } - - public IntroduceeProtocolState next(IntroduceeAction a) { - return this; - } - -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroducerAction.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroducerAction.java deleted file mode 100644 index 7123c7eb2..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroducerAction.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.briarproject.briar.api.introduction; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import javax.annotation.Nullable; - -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; - -@NotNullByDefault -public enum IntroducerAction { - - LOCAL_REQUEST, - LOCAL_ABORT, - REMOTE_ACCEPT_1, - REMOTE_ACCEPT_2, - REMOTE_DECLINE_1, - REMOTE_DECLINE_2, - REMOTE_ABORT, - ACK_1, - ACK_2; - - @Nullable - public static IntroducerAction getLocal(int type) { - if (type == TYPE_REQUEST) return LOCAL_REQUEST; - if (type == TYPE_ABORT) return LOCAL_ABORT; - return null; - } - - @Nullable - public static IntroducerAction getRemote(int type, boolean one, - boolean accept) { - - if (one) { - if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT_1; - if (type == TYPE_RESPONSE) return REMOTE_DECLINE_1; - if (type == TYPE_ACK) return ACK_1; - } else { - if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT_2; - if (type == TYPE_RESPONSE) return REMOTE_DECLINE_2; - if (type == TYPE_ACK) return ACK_2; - } - if (type == TYPE_ABORT) return REMOTE_ABORT; - return null; - } - - @Nullable - public static IntroducerAction getRemote(int type, boolean one) { - return getRemote(type, one, true); - } - -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroducerProtocolState.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroducerProtocolState.java deleted file mode 100644 index b3d89864e..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroducerProtocolState.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.briarproject.briar.api.introduction; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import javax.annotation.concurrent.Immutable; - -import static org.briarproject.briar.api.introduction.IntroducerAction.ACK_1; -import static org.briarproject.briar.api.introduction.IntroducerAction.ACK_2; -import static org.briarproject.briar.api.introduction.IntroducerAction.LOCAL_REQUEST; -import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ABORT; -import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_1; -import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_2; -import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_1; -import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_2; - -@Immutable -@NotNullByDefault -public enum IntroducerProtocolState { - - ERROR(0), - PREPARE_REQUESTS(1) { - @Override - public IntroducerProtocolState next(IntroducerAction a) { - if (a == LOCAL_REQUEST) return AWAIT_RESPONSES; - return ERROR; - } - }, - AWAIT_RESPONSES(2) { - @Override - public IntroducerProtocolState next(IntroducerAction a) { - if (a == REMOTE_ACCEPT_1) return AWAIT_RESPONSE_2; - if (a == REMOTE_ACCEPT_2) return AWAIT_RESPONSE_1; - if (a == REMOTE_DECLINE_1) return FINISHED; - if (a == REMOTE_DECLINE_2) return FINISHED; - return ERROR; - } - }, - AWAIT_RESPONSE_1(3) { - @Override - public IntroducerProtocolState next(IntroducerAction a) { - if (a == REMOTE_ACCEPT_1) return AWAIT_ACKS; - if (a == REMOTE_DECLINE_1) return FINISHED; - return ERROR; - } - }, - AWAIT_RESPONSE_2(4) { - @Override - public IntroducerProtocolState next(IntroducerAction a) { - if (a == REMOTE_ACCEPT_2) return AWAIT_ACKS; - if (a == REMOTE_DECLINE_2) return FINISHED; - return ERROR; - } - }, - AWAIT_ACKS(5) { - @Override - public IntroducerProtocolState next(IntroducerAction a) { - if (a == ACK_1) return AWAIT_ACK_2; - if (a == ACK_2) return AWAIT_ACK_1; - return ERROR; - } - }, - AWAIT_ACK_1(6) { - @Override - public IntroducerProtocolState next(IntroducerAction a) { - if (a == ACK_1) return FINISHED; - return ERROR; - } - }, - AWAIT_ACK_2(7) { - @Override - public IntroducerProtocolState next(IntroducerAction a) { - if (a == ACK_2) return FINISHED; - return ERROR; - } - }, - FINISHED(8) { - @Override - public IntroducerProtocolState next(IntroducerAction a) { - if (a == REMOTE_ABORT) return ERROR; - return FINISHED; - } - }; - - private final int value; - - IntroducerProtocolState(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - - public static IntroducerProtocolState fromValue(int value) { - for (IntroducerProtocolState s : values()) { - if (s.value == value) return s; - } - throw new IllegalArgumentException(); - } - - public static boolean isOngoing(IntroducerProtocolState state) { - return state != FINISHED && state != ERROR; - } - - public IntroducerProtocolState next(IntroducerAction a) { - return this; - } -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java index 72e6030b1..9b454218d 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java @@ -4,126 +4,26 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_L public interface IntroductionConstants { - /* Protocol roles */ - int ROLE_INTRODUCER = 0; - int ROLE_INTRODUCEE = 1; - - /* Message types */ - int TYPE_REQUEST = 1; - int TYPE_RESPONSE = 2; - int TYPE_ACK = 3; - int TYPE_ABORT = 4; - - /* Message Constants */ - String TYPE = "type"; - String GROUP_ID = "groupId"; - String SESSION_ID = "sessionId"; - String CONTACT = "contactId"; - String NAME = "name"; - String PUBLIC_KEY = "publicKey"; - String E_PUBLIC_KEY = "ephemeralPublicKey"; - String MSG = "msg"; - String ACCEPT = "accept"; - String TIME = "time"; - String TRANSPORT = "transport"; - String MESSAGE_ID = "messageId"; - String MESSAGE_TIME = "timestamp"; - String MAC = "mac"; - String SIGNATURE = "signature"; - - /* Validation Constants */ - - /** - * The length of the message authentication code in bytes. - */ - int MAC_LENGTH = 32; - /** * The maximum length of the introducer's optional message to the * introducees in UTF-8 bytes. */ - int MAX_INTRODUCTION_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; + int MAX_REQUEST_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; - /* Introducer Local State Metadata */ - String STATE = "state"; - String ROLE = "role"; - String GROUP_ID_1 = "groupId1"; - String GROUP_ID_2 = "groupId2"; - String CONTACT_1 = "contact1"; - String CONTACT_2 = "contact2"; - String AUTHOR_ID_1 = "authorId1"; - String AUTHOR_ID_2 = "authorId2"; - String CONTACT_ID_1 = "contactId1"; - String CONTACT_ID_2 = "contactId2"; - String RESPONSE_1 = "response1"; - String RESPONSE_2 = "response2"; + String LABEL_SESSION_ID = "org.briarproject.briar.introduction/SESSION_ID"; - /* Introduction Request Action */ - String PUBLIC_KEY1 = "publicKey1"; - String PUBLIC_KEY2 = "publicKey2"; + String LABEL_MASTER_KEY = "org.briarproject.briar.introduction/MASTER_KEY"; - /* Introducee Local State Metadata (without those already defined) */ - String STORAGE_ID = "storageId"; - String INTRODUCER = "introducer"; - String LOCAL_AUTHOR_ID = "localAuthorId"; - String REMOTE_AUTHOR_ID = "remoteAuthorId"; - String OUR_PUBLIC_KEY = "ourEphemeralPublicKey"; - String OUR_PRIVATE_KEY = "ourEphemeralPrivateKey"; - String OUR_TIME = "ourTime"; - String ADDED_CONTACT_ID = "addedContactId"; - String NOT_OUR_RESPONSE = "notOurResponse"; - String EXISTS = "contactExists"; - String REMOTE_AUTHOR_IS_US = "remoteAuthorIsUs"; - String ANSWERED = "answered"; - String NONCE = "nonce"; - String MAC_KEY = "macKey"; - String OUR_TRANSPORT = "ourTransport"; - String OUR_MAC = "ourMac"; - String OUR_SIGNATURE = "ourSignature"; - - String TASK = "task"; - int TASK_ADD_CONTACT = 0; - int TASK_ACTIVATE_CONTACT = 1; - int TASK_ABORT = 2; - - /** - * Label for deriving the shared secret. - */ - String SHARED_SECRET_LABEL = - "org.briarproject.briar.introduction/SHARED_SECRET"; - - /** - * Label for deriving Alice's key binding nonce from the shared secret. - */ - String ALICE_NONCE_LABEL = - "org.briarproject.briar.introduction/ALICE_NONCE"; - - /** - * Label for deriving Bob's key binding nonce from the shared secret. - */ - String BOB_NONCE_LABEL = - "org.briarproject.briar.introduction/BOB_NONCE"; - - /** - * Label for deriving Alice's MAC key from the shared secret. - */ - String ALICE_MAC_KEY_LABEL = + String LABEL_ALICE_MAC_KEY = "org.briarproject.briar.introduction/ALICE_MAC_KEY"; - /** - * Label for deriving Bob's MAC key from the shared secret. - */ - String BOB_MAC_KEY_LABEL = + String LABEL_BOB_MAC_KEY = "org.briarproject.briar.introduction/BOB_MAC_KEY"; - /** - * Label for signing the introduction response. - */ - String SIGNING_LABEL = - "org.briarproject.briar.introduction/RESPONSE_SIGNATURE"; + String LABEL_AUTH_MAC = "org.briarproject.briar.introduction/AUTH_MAC"; + + String LABEL_AUTH_SIGN = "org.briarproject.briar.introduction/AUTH_SIGN"; + + String LABEL_AUTH_NONCE = "org.briarproject.briar.introduction/AUTH_NONCE"; - /** - * Label for MACing the introduction response. - */ - String MAC_LABEL = "org.briarproject.briar.introduction/RESPONSE_MAC"; } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java index ce9d06727..15936e8ad 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java @@ -1,6 +1,5 @@ package org.briarproject.briar.api.introduction; -import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.db.DbException; @@ -24,25 +23,25 @@ public interface IntroductionManager extends ConversationClient { /** * The current version of the introduction client. */ - int CLIENT_VERSION = 0; + int CLIENT_VERSION = 1; /** * Sends two initial introduction messages. */ void makeIntroduction(Contact c1, Contact c2, @Nullable String msg, - long timestamp) throws DbException, FormatException; + long timestamp) throws DbException; /** * Accepts an introduction. */ void acceptIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException, FormatException; + long timestamp) throws DbException; /** * Declines an introduction. */ void declineIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException, FormatException; + long timestamp) throws DbException; /** * Returns all introduction messages for the given contact. diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionMessage.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionMessage.java index 861469a3a..009487fa2 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionMessage.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionMessage.java @@ -8,7 +8,7 @@ import org.briarproject.briar.api.client.SessionId; import javax.annotation.concurrent.Immutable; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER; +import static org.briarproject.briar.api.introduction.Role.INTRODUCER; @Immutable @NotNullByDefault @@ -16,10 +16,10 @@ public class IntroductionMessage extends BaseMessageHeader { private final SessionId sessionId; private final MessageId messageId; - private final int role; + private final Role role; IntroductionMessage(SessionId sessionId, MessageId messageId, - GroupId groupId, int role, long time, boolean local, boolean sent, + GroupId groupId, Role role, long time, boolean local, boolean sent, boolean seen, boolean read) { super(messageId, groupId, time, local, sent, seen, read); @@ -37,7 +37,7 @@ public class IntroductionMessage extends BaseMessageHeader { } public boolean isIntroducer() { - return role == ROLE_INTRODUCER; + return role == INTRODUCER; } } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionRequest.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionRequest.java index 428528260..b2a804bd8 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionRequest.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionRequest.java @@ -1,6 +1,5 @@ package org.briarproject.briar.api.introduction; -import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; @@ -15,21 +14,19 @@ public class IntroductionRequest extends IntroductionResponse { @Nullable private final String message; - private final boolean answered, exists, introducesOtherIdentity; + private final boolean answered, exists; public IntroductionRequest(SessionId sessionId, MessageId messageId, - GroupId groupId, int role, long time, boolean local, boolean sent, - boolean seen, boolean read, AuthorId authorId, String name, - boolean accepted, @Nullable String message, boolean answered, - boolean exists, boolean introducesOtherIdentity) { + GroupId groupId, Role role, long time, boolean local, boolean sent, + boolean seen, boolean read, String name, boolean accepted, + @Nullable String message, boolean answered, boolean exists) { super(sessionId, messageId, groupId, role, time, local, sent, seen, - read, authorId, name, accepted); + read, name, accepted); this.message = message; this.answered = answered; this.exists = exists; - this.introducesOtherIdentity = introducesOtherIdentity; } @Nullable @@ -44,8 +41,4 @@ public class IntroductionRequest extends IntroductionResponse { public boolean contactExists() { return exists; } - - public boolean doesIntroduceOtherIdentity() { - return introducesOtherIdentity; - } } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionResponse.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionResponse.java index 22df6eba8..816135d43 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionResponse.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionResponse.java @@ -1,6 +1,5 @@ package org.briarproject.briar.api.introduction; -import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.MessageId; @@ -12,19 +11,15 @@ import javax.annotation.concurrent.Immutable; @NotNullByDefault public class IntroductionResponse extends IntroductionMessage { - private final AuthorId remoteAuthorId; private final String name; private final boolean accepted; public IntroductionResponse(SessionId sessionId, MessageId messageId, - GroupId groupId, int role, long time, boolean local, boolean sent, - boolean seen, boolean read, AuthorId remoteAuthorId, String name, - boolean accepted) { - + GroupId groupId, Role role, long time, boolean local, boolean sent, + boolean seen, boolean read, String name, boolean accepted) { super(sessionId, messageId, groupId, role, time, local, sent, seen, read); - this.remoteAuthorId = remoteAuthorId; this.name = name; this.accepted = accepted; } @@ -37,7 +32,4 @@ public class IntroductionResponse extends IntroductionMessage { return accepted; } - public AuthorId getRemoteAuthorId() { - return remoteAuthorId; - } } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/Role.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/Role.java similarity index 91% rename from briar-api/src/main/java/org/briarproject/briar/api/introduction2/Role.java rename to briar-api/src/main/java/org/briarproject/briar/api/introduction/Role.java index bc7e4e3d9..38f0bd44c 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/Role.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/Role.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.api.introduction2; +package org.briarproject.briar.api.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionAbortedEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionAbortedEvent.java index 585527591..113ba400e 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionAbortedEvent.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionAbortedEvent.java @@ -1,6 +1,5 @@ package org.briarproject.briar.api.introduction.event; -import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.api.client.SessionId; @@ -11,19 +10,14 @@ import javax.annotation.concurrent.Immutable; @NotNullByDefault public class IntroductionAbortedEvent extends Event { - private final ContactId contactId; private final SessionId sessionId; - public IntroductionAbortedEvent(ContactId contactId, SessionId sessionId) { - this.contactId = contactId; + public IntroductionAbortedEvent(SessionId sessionId) { this.sessionId = sessionId; } - public ContactId getContactId() { - return contactId; - } - public SessionId getSessionId() { return sessionId; } + } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionSucceededEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionSucceededEvent.java index 88998789c..44d166863 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionSucceededEvent.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionSucceededEvent.java @@ -8,6 +8,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault +// TODO still needed? public class IntroductionSucceededEvent extends Event { private final Contact contact; diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java deleted file mode 100644 index 962bd0a52..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.briarproject.briar.api.introduction2; - -import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; - -public interface IntroductionConstants { - - /** - * The maximum length of the introducer's optional message to the - * introducees in UTF-8 bytes. - */ - int MAX_REQUEST_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; - - String LABEL_SESSION_ID = "org.briarproject.briar.introduction/SESSION_ID"; - - String LABEL_MASTER_KEY = "org.briarproject.briar.introduction/MASTER_KEY"; - - String LABEL_ALICE_MAC_KEY = - "org.briarproject.briar.introduction/ALICE_MAC_KEY"; - - String LABEL_BOB_MAC_KEY = - "org.briarproject.briar.introduction/BOB_MAC_KEY"; - - String LABEL_AUTH_MAC = "org.briarproject.briar.introduction/AUTH_MAC"; - - String LABEL_AUTH_SIGN = "org.briarproject.briar.introduction/AUTH_SIGN"; - - String LABEL_AUTH_NONCE = "org.briarproject.briar.introduction/AUTH_NONCE"; - -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionManager.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionManager.java deleted file mode 100644 index f3d5c40fa..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionManager.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.briarproject.briar.api.introduction2; - -import org.briarproject.bramble.api.contact.Contact; -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.sync.ClientId; -import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.api.messaging.ConversationManager.ConversationClient; - -import java.util.Collection; - -import javax.annotation.Nullable; - -@NotNullByDefault -public interface IntroductionManager extends ConversationClient { - - /** - * The unique ID of the introduction client. - */ - ClientId CLIENT_ID = new ClientId("org.briarproject.briar.introduction"); - - /** - * The current version of the introduction client. - */ - int CLIENT_VERSION = 1; - - /** - * Sends two initial introduction messages. - */ - void makeIntroduction(Contact c1, Contact c2, @Nullable String msg, - long timestamp) throws DbException; - - /** - * Accepts an introduction. - */ - void acceptIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException; - - /** - * Declines an introduction. - */ - void declineIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException; - - /** - * Returns all introduction messages for the given contact. - */ - Collection getIntroductionMessages(ContactId contactId) - throws DbException; - -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionMessage.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionMessage.java deleted file mode 100644 index a4b3999d1..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionMessage.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.briarproject.briar.api.introduction2; - -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.BaseMessageHeader; -import org.briarproject.briar.api.client.SessionId; - -import javax.annotation.concurrent.Immutable; - -import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; - -@Immutable -@NotNullByDefault -public class IntroductionMessage extends BaseMessageHeader { - - private final SessionId sessionId; - private final MessageId messageId; - private final Role role; - - IntroductionMessage(SessionId sessionId, MessageId messageId, - GroupId groupId, Role role, long time, boolean local, boolean sent, - boolean seen, boolean read) { - - super(messageId, groupId, time, local, sent, seen, read); - this.sessionId = sessionId; - this.messageId = messageId; - this.role = role; - } - - public SessionId getSessionId() { - return sessionId; - } - - public MessageId getMessageId() { - return messageId; - } - - public boolean isIntroducer() { - return role == INTRODUCER; - } - -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionRequest.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionRequest.java deleted file mode 100644 index 4494fa123..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionRequest.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.briarproject.briar.api.introduction2; - -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 -public class IntroductionRequest extends IntroductionResponse { - - @Nullable - private final String message; - private final boolean answered, exists; - - public IntroductionRequest(SessionId sessionId, MessageId messageId, - GroupId groupId, Role role, long time, boolean local, boolean sent, - boolean seen, boolean read, String name, boolean accepted, - @Nullable String message, boolean answered, boolean exists) { - - super(sessionId, messageId, groupId, role, time, local, sent, seen, - read, name, accepted); - - this.message = message; - this.answered = answered; - this.exists = exists; - } - - @Nullable - public String getMessage() { - return message; - } - - public boolean wasAnswered() { - return answered; - } - - public boolean contactExists() { - return exists; - } -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionResponse.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionResponse.java deleted file mode 100644 index b9d2bd993..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionResponse.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.briarproject.briar.api.introduction2; - -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.concurrent.Immutable; - -@Immutable -@NotNullByDefault -public class IntroductionResponse extends IntroductionMessage { - - private final String name; - private final boolean accepted; - - public IntroductionResponse(SessionId sessionId, MessageId messageId, - GroupId groupId, Role role, long time, boolean local, boolean sent, - boolean seen, boolean read, String name, boolean accepted) { - super(sessionId, messageId, groupId, role, time, local, sent, seen, - read); - - this.name = name; - this.accepted = accepted; - } - - public String getName() { - return name; - } - - public boolean wasAccepted() { - return accepted; - } - -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionAbortedEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionAbortedEvent.java deleted file mode 100644 index 610db3970..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionAbortedEvent.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.briarproject.briar.api.introduction2.event; - -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.identity.AuthorId; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.briar.api.client.SessionId; - -import javax.annotation.concurrent.Immutable; - -@Immutable -@NotNullByDefault -// TODO still needed? -public class IntroductionAbortedEvent extends Event { - - private final AuthorId remoteAuthorId; - private final SessionId sessionId; - - public IntroductionAbortedEvent(AuthorId remoteAuthorId, - SessionId sessionId) { - this.remoteAuthorId = remoteAuthorId; - this.sessionId = sessionId; - } - - public AuthorId getRemoteAuthorId() { - return remoteAuthorId; - } - - public SessionId getSessionId() { - return sessionId; - } - -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionRequestReceivedEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionRequestReceivedEvent.java deleted file mode 100644 index 0798ac356..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionRequestReceivedEvent.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.briarproject.briar.api.introduction2.event; - -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.briar.api.introduction2.IntroductionRequest; - -import javax.annotation.concurrent.Immutable; - -@Immutable -@NotNullByDefault -public class IntroductionRequestReceivedEvent extends Event { - - private final ContactId contactId; - private final IntroductionRequest introductionRequest; - - public IntroductionRequestReceivedEvent(ContactId contactId, - IntroductionRequest introductionRequest) { - - this.contactId = contactId; - this.introductionRequest = introductionRequest; - } - - public ContactId getContactId() { - return contactId; - } - - public IntroductionRequest getIntroductionRequest() { - return introductionRequest; - } - -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionResponseReceivedEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionResponseReceivedEvent.java deleted file mode 100644 index a05731b59..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionResponseReceivedEvent.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.briarproject.briar.api.introduction2.event; - -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.briar.api.introduction2.IntroductionResponse; - -import javax.annotation.concurrent.Immutable; - -@Immutable -@NotNullByDefault -public class IntroductionResponseReceivedEvent extends Event { - - private final ContactId contactId; - private final IntroductionResponse introductionResponse; - - public IntroductionResponseReceivedEvent(ContactId contactId, - IntroductionResponse introductionResponse) { - - this.contactId = contactId; - this.introductionResponse = introductionResponse; - } - - public ContactId getContactId() { - return contactId; - } - - public IntroductionResponse getIntroductionResponse() { - return introductionResponse; - } -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionSucceededEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionSucceededEvent.java deleted file mode 100644 index 11f85c2ce..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/event/IntroductionSucceededEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.briarproject.briar.api.introduction2.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 -// TODO still needed? -public class IntroductionSucceededEvent extends Event { - - private final Contact contact; - - public IntroductionSucceededEvent(Contact contact) { - this.contact = contact; - } - - public Contact getContact() { - return contact; - } -} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/AbortMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AbortMessage.java similarity index 86% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/AbortMessage.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/AbortMessage.java index 9d7112407..e9a2d1233 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/AbortMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AbortMessage.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; @@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -class AbortMessage extends IntroductionMessage { +class AbortMessage extends AbstractIntroductionMessage { private final SessionId sessionId; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractIntroductionMessage.java similarity index 84% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionMessage.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/AbstractIntroductionMessage.java index 24a252f5e..240b5ddec 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractIntroductionMessage.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; @@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -abstract class IntroductionMessage { +abstract class AbstractIntroductionMessage { private final MessageId messageId; private final GroupId groupId; @@ -17,7 +17,7 @@ abstract class IntroductionMessage { @Nullable private final MessageId previousMessageId; - IntroductionMessage(MessageId messageId, GroupId groupId, + AbstractIntroductionMessage(MessageId messageId, GroupId groupId, long timestamp, @Nullable MessageId previousMessageId) { this.messageId = messageId; this.groupId = groupId; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java similarity index 90% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/AbstractProtocolEngine.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java index d2c9f2270..20ac59307 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/AbstractProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; @@ -25,14 +25,14 @@ import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_ID; -import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION; -import static org.briarproject.briar.introduction2.MessageType.ABORT; -import static org.briarproject.briar.introduction2.MessageType.ACCEPT; -import static org.briarproject.briar.introduction2.MessageType.ACTIVATE; -import static org.briarproject.briar.introduction2.MessageType.AUTH; -import static org.briarproject.briar.introduction2.MessageType.DECLINE; -import static org.briarproject.briar.introduction2.MessageType.REQUEST; +import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; +import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION; +import static org.briarproject.briar.introduction.MessageType.ABORT; +import static org.briarproject.briar.introduction.MessageType.ACCEPT; +import static org.briarproject.briar.introduction.MessageType.ACTIVATE; +import static org.briarproject.briar.introduction.MessageType.AUTH; +import static org.briarproject.briar.introduction.MessageType.DECLINE; +import static org.briarproject.briar.introduction.MessageType.REQUEST; @Immutable @NotNullByDefault diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AcceptMessage.java similarity index 93% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/AcceptMessage.java index 77d09c74a..6cadae73b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/AcceptMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AcceptMessage.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; @@ -14,7 +14,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -class AcceptMessage extends IntroductionMessage { +class AcceptMessage extends AbstractIntroductionMessage { private final SessionId sessionId; private final byte[] ephemeralPublicKey; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/ActivateMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java similarity index 85% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/ActivateMessage.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java index 0d5e935b4..e7498dec2 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/ActivateMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; @@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -class ActivateMessage extends IntroductionMessage { +class ActivateMessage extends AbstractIntroductionMessage { private final SessionId sessionId; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/AuthMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AuthMessage.java similarity index 89% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/AuthMessage.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/AuthMessage.java index 5833a64d6..1de1a4eb5 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/AuthMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AuthMessage.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; @@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -class AuthMessage extends IntroductionMessage { +class AuthMessage extends AbstractIntroductionMessage { private final SessionId sessionId; private final byte[] mac, signature; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/DeclineMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction/DeclineMessage.java similarity index 86% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/DeclineMessage.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/DeclineMessage.java index c419704c5..27386b905 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/DeclineMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/DeclineMessage.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; @@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -class DeclineMessage extends IntroductionMessage { +class DeclineMessage extends AbstractIntroductionMessage { private final SessionId sessionId; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeEngine.java deleted file mode 100644 index 2d5bb95c3..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeEngine.java +++ /dev/null @@ -1,372 +0,0 @@ -package org.briarproject.briar.introduction; - -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.data.BdfDictionary; -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.identity.AuthorId; -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.ProtocolEngine; -import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.api.introduction.IntroduceeAction; -import org.briarproject.briar.api.introduction.IntroduceeProtocolState; -import org.briarproject.briar.api.introduction.IntroductionRequest; -import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; -import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.logging.Logger; - -import javax.annotation.concurrent.Immutable; - -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.WARNING; -import static org.briarproject.briar.api.introduction.IntroduceeAction.ACK; -import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_ABORT; -import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_ACCEPT; -import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_DECLINE; -import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_ABORT; -import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_ACK; -import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REMOTE_RESPONSE; -import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST; -import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_RESPONSES; -import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.ERROR; -import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.FINISHED; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS; -import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_MAC; -import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_SIGNATURE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ACTIVATE_CONTACT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ADD_CONTACT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; - -@Immutable -@NotNullByDefault -class IntroduceeEngine - implements ProtocolEngine { - - private static final Logger LOG = - Logger.getLogger(IntroduceeEngine.class.getName()); - - @Override - public StateUpdate onLocalAction( - BdfDictionary localState, BdfDictionary localAction) { - - try { - IntroduceeProtocolState currentState = - getState(localState.getLong(STATE)); - int type = localAction.getLong(TYPE).intValue(); - IntroduceeAction action; - if (localState.containsKey(ACCEPT)) action = IntroduceeAction - .getLocal(type, localState.getBoolean(ACCEPT)); - else action = IntroduceeAction.getLocal(type); - IntroduceeProtocolState nextState = currentState.next(action); - - if (action == LOCAL_ABORT && currentState != ERROR) { - return abortSession(currentState, localState); - } - - if (nextState == ERROR) { - if (LOG.isLoggable(WARNING)) { - LOG.warning("Error: Invalid action in state " + - currentState.name()); - } - if (currentState == ERROR) return noUpdate(localState); - else return abortSession(currentState, localState); - } - - List messages = new ArrayList<>(1); - if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) { - localState.put(STATE, nextState.getValue()); - localState.put(ANSWERED, true); - // create the introduction response message - BdfDictionary msg = new BdfDictionary(); - msg.put(TYPE, TYPE_RESPONSE); - msg.put(GROUP_ID, localState.getRaw(GROUP_ID)); - msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); - msg.put(ACCEPT, localState.getBoolean(ACCEPT)); - if (localState.getBoolean(ACCEPT)) { - msg.put(TIME, localState.getLong(OUR_TIME)); - msg.put(E_PUBLIC_KEY, localState.getRaw(OUR_PUBLIC_KEY)); - msg.put(TRANSPORT, localAction.getDictionary(TRANSPORT)); - } - msg.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME)); - messages.add(msg); - logAction(currentState, localState, msg); - - if (nextState == AWAIT_ACK) { - localState.put(TASK, TASK_ADD_CONTACT); - } - } else if (action == ACK) { - // just send ACK, don't update local state again - BdfDictionary ack = getAckMessage(localState); - messages.add(ack); - } else { - throw new IllegalArgumentException(); - } - List events = Collections.emptyList(); - return new StateUpdate<>(false, false, - localState, messages, events); - } catch (FormatException e) { - throw new IllegalArgumentException(e); - } - } - - @Override - public StateUpdate onMessageReceived( - BdfDictionary localState, BdfDictionary msg) { - - try { - IntroduceeProtocolState currentState = - getState(localState.getLong(STATE)); - int type = msg.getLong(TYPE).intValue(); - IntroduceeAction action = IntroduceeAction.getRemote(type); - IntroduceeProtocolState nextState = currentState.next(action); - - logMessageReceived(currentState, nextState, localState, type, msg); - - if (nextState == ERROR) { - if (currentState != ERROR && action != REMOTE_ABORT) { - return abortSession(currentState, localState); - } else { - return noUpdate(localState); - } - } - - // update local session state with next protocol state - localState.put(STATE, nextState.getValue()); - List messages; - List events; - // we received the introduction request - if (currentState == AWAIT_REQUEST) { - // remember the session ID used by the introducer - localState.put(SESSION_ID, msg.getRaw(SESSION_ID)); - - addRequestData(localState, msg); - messages = Collections.emptyList(); - events = Collections.singletonList(getEvent(localState, msg)); - } - // we had the request and now one response came in _OR_ - // we had sent our response already and now received the other one - else if (currentState == AWAIT_RESPONSES || - currentState == AWAIT_REMOTE_RESPONSE) { - // update next state based on message content - action = IntroduceeAction - .getRemote(type, msg.getBoolean(ACCEPT)); - nextState = currentState.next(action); - localState.put(STATE, nextState.getValue()); - - addResponseData(localState, msg); - if (nextState == AWAIT_ACK) { - localState.put(TASK, TASK_ADD_CONTACT); - } - messages = Collections.emptyList(); - events = Collections.emptyList(); - } - // we already sent our ACK and now received the other one - else if (currentState == AWAIT_ACK) { - localState.put(TASK, TASK_ACTIVATE_CONTACT); - addAckData(localState, msg); - messages = Collections.emptyList(); - events = Collections.emptyList(); - } - // we are done (probably declined response), ignore & delete message - else if (currentState == FINISHED) { - return new StateUpdate<>(true, false, localState, - Collections.emptyList(), - Collections.emptyList()); - } - // this should not happen - else { - throw new IllegalArgumentException(); - } - return new StateUpdate<>(false, false, - localState, messages, events); - } catch (FormatException e) { - throw new IllegalArgumentException(e); - } - } - - private void addRequestData(BdfDictionary localState, BdfDictionary msg) - throws FormatException { - - localState.put(NAME, msg.getString(NAME)); - localState.put(PUBLIC_KEY, msg.getRaw(PUBLIC_KEY)); - if (msg.containsKey(MSG)) { - localState.put(MSG, msg.getString(MSG)); - } - } - - private void addResponseData(BdfDictionary localState, BdfDictionary msg) - throws FormatException { - - if (localState.containsKey(ACCEPT)) { - localState.put(ACCEPT, - localState.getBoolean(ACCEPT) && msg.getBoolean(ACCEPT)); - } else { - localState.put(ACCEPT, msg.getBoolean(ACCEPT)); - } - localState.put(NOT_OUR_RESPONSE, msg.getRaw(MESSAGE_ID)); - - if (msg.getBoolean(ACCEPT)) { - localState.put(TIME, msg.getLong(TIME)); - localState.put(E_PUBLIC_KEY, msg.getRaw(E_PUBLIC_KEY)); - localState.put(TRANSPORT, msg.getDictionary(TRANSPORT)); - } - } - - private void addAckData(BdfDictionary localState, BdfDictionary msg) - throws FormatException { - - localState.put(MAC, msg.getRaw(MAC)); - localState.put(SIGNATURE, msg.getRaw(SIGNATURE)); - } - - private BdfDictionary getAckMessage(BdfDictionary localState) - throws FormatException { - - BdfDictionary m = new BdfDictionary(); - m.put(TYPE, TYPE_ACK); - m.put(GROUP_ID, localState.getRaw(GROUP_ID)); - m.put(SESSION_ID, localState.getRaw(SESSION_ID)); - m.put(MAC, localState.getRaw(OUR_MAC)); - m.put(SIGNATURE, localState.getRaw(OUR_SIGNATURE)); - return m; - } - - private void logAction(IntroduceeProtocolState state, - BdfDictionary localState, BdfDictionary msg) { - - if (!LOG.isLoggable(INFO)) return; - - try { - LOG.info("Sending " + - (localState.getBoolean(ACCEPT) ? "accept " : "decline ") + - "response in state " + state.name()); - LOG.info("Moving on to state " + - getState(localState.getLong(STATE)).name()); - } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } - } - - private void logMessageReceived(IntroduceeProtocolState currentState, - IntroduceeProtocolState nextState, BdfDictionary localState, - int type, BdfDictionary msg) { - - if (!LOG.isLoggable(INFO)) return; - - String t = "unknown"; - if (type == TYPE_REQUEST) t = "Introduction"; - else if (type == TYPE_RESPONSE) t = "Response"; - else if (type == TYPE_ACK) t = "ACK"; - else if (type == TYPE_ABORT) t = "Abort"; - - LOG.info("Received " + t + " in state " + currentState.name()); - LOG.info("Moving on to state " + nextState.name()); - } - - @Override - public StateUpdate onMessageDelivered( - BdfDictionary localState, BdfDictionary delivered) { - try { - return noUpdate(localState); - } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - return null; - } - } - - private IntroduceeProtocolState getState(Long state) { - return IntroduceeProtocolState.fromValue(state.intValue()); - } - - private Event getEvent(BdfDictionary localState, BdfDictionary msg) - throws FormatException { - - ContactId contactId = - new ContactId(localState.getLong(CONTACT_ID_1).intValue()); - AuthorId authorId = new AuthorId(localState.getRaw(REMOTE_AUTHOR_ID)); - - SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); - MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID)); - GroupId groupId = new GroupId(msg.getRaw(GROUP_ID)); - long time = msg.getLong(MESSAGE_TIME); - String name = msg.getString(NAME); - String message = msg.getOptionalString(MSG); - boolean exists = localState.getBoolean(EXISTS); - boolean introducesOtherIdentity = - localState.getBoolean(REMOTE_AUTHOR_IS_US); - - IntroductionRequest ir = new IntroductionRequest(sessionId, messageId, - groupId, ROLE_INTRODUCEE, time, false, false, false, false, - authorId, name, false, message, false, exists, - introducesOtherIdentity); - return new IntroductionRequestReceivedEvent(contactId, ir); - } - - private StateUpdate abortSession( - IntroduceeProtocolState currentState, BdfDictionary localState) - throws FormatException { - - if (LOG.isLoggable(WARNING)) - LOG.warning("Aborting protocol session in state " + - currentState.name()); - - localState.put(STATE, ERROR.getValue()); - localState.put(TASK, TASK_ABORT); - BdfDictionary msg = new BdfDictionary(); - msg.put(TYPE, TYPE_ABORT); - msg.put(GROUP_ID, localState.getRaw(GROUP_ID)); - msg.put(SESSION_ID, localState.getRaw(SESSION_ID)); - List messages = Collections.singletonList(msg); - - // send abort event - ContactId contactId = - new ContactId(localState.getLong(CONTACT_ID_1).intValue()); - SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); - Event event = new IntroductionAbortedEvent(contactId, sessionId); - List events = Collections.singletonList(event); - - return new StateUpdate<>(false, false, localState, messages, events); - } - - private StateUpdate noUpdate( - BdfDictionary localState) throws FormatException { - - return new StateUpdate<>(false, false, localState, - Collections.emptyList(), - Collections.emptyList()); - } -} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeManager.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeManager.java deleted file mode 100644 index 4666ac92e..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeManager.java +++ /dev/null @@ -1,569 +0,0 @@ -package org.briarproject.briar.introduction; - -import org.briarproject.bramble.api.Bytes; -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.client.ClientHelper; -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.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.BdfDictionary; -import org.briarproject.bramble.api.data.BdfList; -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.event.Event; -import org.briarproject.bramble.api.identity.Author; -import org.briarproject.bramble.api.identity.AuthorFactory; -import org.briarproject.bramble.api.identity.AuthorId; -import org.briarproject.bramble.api.identity.IdentityManager; -import org.briarproject.bramble.api.identity.LocalAuthor; -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.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.briar.api.introduction.event.IntroductionSucceededEvent; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Logger; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import javax.inject.Inject; - -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.WARNING; -import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; -import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ADDED_CONTACT_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_MAC_KEY_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_NONCE_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED; -import static org.briarproject.briar.api.introduction.IntroductionConstants.BOB_MAC_KEY_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.BOB_NONCE_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS; -import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.INTRODUCER; -import static org.briarproject.briar.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_MAC; -import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_PRIVATE_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_SIGNATURE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_TRANSPORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SHARED_SECRET_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ACTIVATE_CONTACT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ADD_CONTACT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION; - -@Immutable -@NotNullByDefault -class IntroduceeManager { - - private static final Logger LOG = - Logger.getLogger(IntroduceeManager.class.getName()); - - private final MessageSender messageSender; - private final DatabaseComponent db; - private final ClientHelper clientHelper; - private final Clock clock; - private final CryptoComponent cryptoComponent; - private final TransportPropertyManager transportPropertyManager; - private final AuthorFactory authorFactory; - private final ContactManager contactManager; - private final IdentityManager identityManager; - private final IntroductionGroupFactory introductionGroupFactory; - - @Inject - IntroduceeManager(MessageSender messageSender, DatabaseComponent db, - ClientHelper clientHelper, Clock clock, - CryptoComponent cryptoComponent, - TransportPropertyManager transportPropertyManager, - AuthorFactory authorFactory, ContactManager contactManager, - IdentityManager identityManager, - IntroductionGroupFactory introductionGroupFactory) { - - this.messageSender = messageSender; - this.db = db; - this.clientHelper = clientHelper; - this.clock = clock; - this.cryptoComponent = cryptoComponent; - this.transportPropertyManager = transportPropertyManager; - this.authorFactory = authorFactory; - this.contactManager = contactManager; - this.identityManager = identityManager; - this.introductionGroupFactory = introductionGroupFactory; - } - - public BdfDictionary initialize(Transaction txn, GroupId groupId, - BdfDictionary message) throws DbException, FormatException { - - // create local message to keep engine state - long now = clock.currentTimeMillis(); - Bytes salt = new Bytes(new byte[64]); - cryptoComponent.getSecureRandom().nextBytes(salt.getBytes()); - - Message localMsg = clientHelper.createMessage( - introductionGroupFactory.createLocalGroup().getId(), now, - BdfList.of(salt)); - MessageId storageId = localMsg.getId(); - - // find out who is introducing us - BdfDictionary gd = - clientHelper.getGroupMetadataAsDictionary(txn, groupId); - ContactId introducerId = - new ContactId(gd.getLong(CONTACT).intValue()); - Contact introducer = db.getContact(txn, introducerId); - - BdfDictionary d = new BdfDictionary(); - d.put(STORAGE_ID, storageId); - d.put(STATE, AWAIT_REQUEST.getValue()); - d.put(ROLE, ROLE_INTRODUCEE); - d.put(GROUP_ID, groupId); - d.put(INTRODUCER, introducer.getAuthor().getName()); - d.put(CONTACT_ID_1, introducer.getId().getInt()); - d.put(LOCAL_AUTHOR_ID, introducer.getLocalAuthorId().getBytes()); - d.put(NOT_OUR_RESPONSE, storageId); - d.put(ANSWERED, false); - - // check if the contact we are introduced to does already exist - // TODO: Exchange author format version - AuthorId remoteAuthorId = authorFactory - .createAuthor(message.getString(NAME), - message.getRaw(PUBLIC_KEY)).getId(); - boolean exists = contactManager.contactExists(txn, remoteAuthorId, - introducer.getLocalAuthorId()); - d.put(EXISTS, exists); - d.put(REMOTE_AUTHOR_ID, remoteAuthorId); - - // check if someone is trying to introduce us to ourselves - if (remoteAuthorId.equals(introducer.getLocalAuthorId())) { - LOG.warning("Received Introduction Request to Ourselves"); - throw new FormatException(); - } - - // check if remote author is actually one of our other identities - boolean introducesOtherIdentity = - db.containsLocalAuthor(txn, remoteAuthorId); - d.put(REMOTE_AUTHOR_IS_US, introducesOtherIdentity); - - // save local state to database - clientHelper.addLocalMessage(txn, localMsg, d, false); - - return d; - } - - public void incomingMessage(Transaction txn, BdfDictionary state, - BdfDictionary message) throws DbException, FormatException { - - IntroduceeEngine engine = new IntroduceeEngine(); - processStateUpdate(txn, message, - engine.onMessageReceived(state, message)); - } - - void acceptIntroduction(Transaction txn, BdfDictionary state, - long timestamp) throws DbException, FormatException { - - // get data to connect and derive a shared secret later - long now = clock.currentTimeMillis(); - KeyPair keyPair = cryptoComponent.generateAgreementKeyPair(); - byte[] publicKey = keyPair.getPublic().getEncoded(); - byte[] privateKey = keyPair.getPrivate().getEncoded(); - Map transportProperties = - transportPropertyManager.getLocalProperties(txn); - BdfDictionary tp = encodeTransportProperties(transportProperties); - - // update session state for later - state.put(ACCEPT, true); - state.put(OUR_TIME, now); - state.put(OUR_PUBLIC_KEY, publicKey); - state.put(OUR_PRIVATE_KEY, privateKey); - state.put(OUR_TRANSPORT, tp); - - // define action - BdfDictionary localAction = new BdfDictionary(); - localAction.put(TYPE, TYPE_RESPONSE); - localAction.put(TRANSPORT, tp); - localAction.put(MESSAGE_TIME, timestamp); - - // start engine and process its state update - IntroduceeEngine engine = new IntroduceeEngine(); - processStateUpdate(txn, null, engine.onLocalAction(state, localAction)); - } - - void declineIntroduction(Transaction txn, BdfDictionary state, - long timestamp) throws DbException, FormatException { - - // update session state - state.put(ACCEPT, false); - - // define action - BdfDictionary localAction = new BdfDictionary(); - localAction.put(TYPE, TYPE_RESPONSE); - localAction.put(MESSAGE_TIME, timestamp); - - // start engine and process its state update - IntroduceeEngine engine = new IntroduceeEngine(); - processStateUpdate(txn, null, - engine.onLocalAction(state, localAction)); - } - - private void processStateUpdate(Transaction txn, - @Nullable BdfDictionary msg, - IntroduceeEngine.StateUpdate result) - throws DbException, FormatException { - - // perform actions based on new local state - BdfDictionary followUpAction = performTasks(txn, result.localState); - - // save new local state - MessageId storageId = - new MessageId(result.localState.getRaw(STORAGE_ID)); - clientHelper.mergeMessageMetadata(txn, storageId, result.localState); - - // send messages - for (BdfDictionary d : result.toSend) { - messageSender.sendMessage(txn, d); - } - - // broadcast events - for (Event event : result.toBroadcast) { - txn.attach(event); - } - - // delete message - if (result.deleteMessage && msg != null) { - MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID)); - if (LOG.isLoggable(INFO)) { - LOG.info("Deleting message with id " + messageId.hashCode()); - } - db.deleteMessage(txn, messageId); - db.deleteMessageMetadata(txn, messageId); - } - - // process follow up action at the end if available - if (followUpAction != null) { - IntroduceeEngine engine = new IntroduceeEngine(); - processStateUpdate(txn, null, - engine.onLocalAction(result.localState, followUpAction)); - } - } - - @Nullable - private BdfDictionary performTasks(Transaction txn, - BdfDictionary localState) throws FormatException, DbException { - - if (!localState.containsKey(TASK) || localState.get(TASK) == NULL_VALUE) - return null; - - // remember task and remove it from localState - long task = localState.getLong(TASK); - localState.put(TASK, NULL_VALUE); - - if (task == TASK_ADD_CONTACT) { - if (localState.getBoolean(EXISTS)) { - // we have this contact already, so do not perform actions - LOG.info("We have this contact already, do not add"); - return null; - } - - // figure out who takes which role by comparing public keys - byte[] ourPublicKeyBytes = localState.getRaw(OUR_PUBLIC_KEY); - byte[] theirPublicKeyBytes = localState.getRaw(E_PUBLIC_KEY); - int comp = Bytes.COMPARATOR.compare(new Bytes(ourPublicKeyBytes), - new Bytes(theirPublicKeyBytes)); - boolean alice = comp < 0; - - // get our local author - LocalAuthor author = identityManager.getLocalAuthor(txn); - - SecretKey secretKey; - byte[] ourPrivateKeyBytes = localState.getRaw(OUR_PRIVATE_KEY); - try { - // derive secret master key - secretKey = deriveSecretKey(ourPublicKeyBytes, - ourPrivateKeyBytes, alice, theirPublicKeyBytes); - // derive MAC keys and nonces, sign our nonce and calculate MAC - deriveMacKeysAndNonces(localState, author, secretKey, alice); - } catch (GeneralSecurityException e) { - // we can not continue without the signature - throw new DbException(e); - } - - LOG.info("Adding contact in inactive state"); - - // The agreed timestamp is the minimum of the peers' timestamps - long ourTime = localState.getLong(OUR_TIME); - long theirTime = localState.getLong(TIME); - long timestamp = Math.min(ourTime, theirTime); - - // Add the contact to the database as inactive - // TODO: Exchange author format version - Author remoteAuthor = authorFactory - .createAuthor(localState.getString(NAME), - localState.getRaw(PUBLIC_KEY)); - ContactId contactId = contactManager - .addContact(txn, remoteAuthor, author.getId(), secretKey, - timestamp, alice, false, false); - - // Update local state with ContactId, so we know what to activate - localState.put(ADDED_CONTACT_ID, contactId.getInt()); - - // let the transport manager know how to connect to the contact - Map transportProperties = - parseTransportProperties(localState); - transportPropertyManager.addRemoteProperties(txn, contactId, - transportProperties); - - // delete the ephemeral private key by overwriting with NULL value - // this ensures future ephemeral keys can not be recovered when - // this device should gets compromised - localState.put(OUR_PRIVATE_KEY, NULL_VALUE); - - // define next action: Send ACK - BdfDictionary localAction = new BdfDictionary(); - localAction.put(TYPE, TYPE_ACK); - - // return follow up action to start engine - // and process its state update again - return localAction; - } - - // we sent and received an ACK, so activate contact - if (task == TASK_ACTIVATE_CONTACT) { - if (!localState.getBoolean(EXISTS) && - localState.containsKey(ADDED_CONTACT_ID)) { - try { - LOG.info("Verifying Signature..."); - verifySignature(localState); - LOG.info("Verifying MAC..."); - verifyMac(localState); - } catch (GeneralSecurityException e) { - throw new DbException(e); - } - - LOG.info("Activating Contact..."); - - ContactId contactId = new ContactId( - localState.getLong(ADDED_CONTACT_ID).intValue()); - - // activate and show contact in contact list - contactManager.setContactActive(txn, contactId, true); - - // broadcast event informing of successful introduction - Contact contact = db.getContact(txn, contactId); - Event event = new IntroductionSucceededEvent(contact); - txn.attach(event); - } else { - LOG.info( - "We must have had this contact already, not activating..."); - } - } - - // we need to abort the protocol, clean up what has been done - if (task == TASK_ABORT) { - if (localState.containsKey(ADDED_CONTACT_ID)) { - LOG.info("Deleting added contact due to abort..."); - ContactId contactId = new ContactId( - localState.getLong(ADDED_CONTACT_ID).intValue()); - contactManager.removeContact(txn, contactId); - } - } - return null; - } - - private SecretKey deriveSecretKey(byte[] ourPublicKeyBytes, - byte[] ourPrivateKeyBytes, boolean alice, - byte[] theirPublicKeyBytes) throws GeneralSecurityException { - // parse the local ephemeral key pair - KeyParser keyParser = cryptoComponent.getAgreementKeyParser(); - PublicKey ourPublicKey; - PrivateKey ourPrivateKey; - try { - ourPublicKey = keyParser.parsePublicKey(ourPublicKeyBytes); - ourPrivateKey = keyParser.parsePrivateKey(ourPrivateKeyBytes); - } catch (GeneralSecurityException e) { - if (LOG.isLoggable(WARNING)) { - LOG.log(WARNING, e.toString(), e); - } - throw new RuntimeException("Our own ephemeral key is invalid"); - } - KeyPair ourKeyPair = new KeyPair(ourPublicKey, ourPrivateKey); - PublicKey theirPublicKey = - keyParser.parsePublicKey(theirPublicKeyBytes); - - // The shared secret is derived from the local ephemeral key pair - // and the remote ephemeral public key - byte[][] inputs = { - new byte[] {CLIENT_VERSION}, - alice ? ourPublicKeyBytes : theirPublicKeyBytes, - alice ? theirPublicKeyBytes : ourPublicKeyBytes - }; - return cryptoComponent.deriveSharedSecret(SHARED_SECRET_LABEL, - theirPublicKey, ourKeyPair, inputs); - } - - /** - * Derives nonces, signs our nonce and calculates MAC - *

- * Derives two nonces and two MAC keys from the shared secret key. - * The other introducee's nonce and MAC key are added to the localState. - *

- * Our nonce is signed with the local author's long-term private key. - * The signature is added to the localState. - *

- * Calculates a MAC and stores it in the localState. - */ - private void deriveMacKeysAndNonces(BdfDictionary localState, - LocalAuthor author, SecretKey secretKey, boolean alice) - throws FormatException, GeneralSecurityException { - // Derive two nonces and two MAC keys from the shared secret key - String ourNonceLabel = alice ? ALICE_NONCE_LABEL : BOB_NONCE_LABEL; - String theirNonceLabel = alice ? BOB_NONCE_LABEL : ALICE_NONCE_LABEL; - byte[] ourNonce = cryptoComponent.mac(ourNonceLabel, secretKey); - byte[] theirNonce = cryptoComponent.mac(theirNonceLabel, secretKey); - String ourKeyLabel = alice ? ALICE_MAC_KEY_LABEL : BOB_MAC_KEY_LABEL; - String theirKeyLabel = alice ? BOB_MAC_KEY_LABEL : ALICE_MAC_KEY_LABEL; - SecretKey ourMacKey = cryptoComponent.deriveKey(ourKeyLabel, secretKey); - SecretKey theirMacKey = - cryptoComponent.deriveKey(theirKeyLabel, secretKey); - - // Save the other nonce and MAC key for the verification - localState.put(NONCE, theirNonce); - localState.put(MAC_KEY, theirMacKey.getBytes()); - - // Sign our nonce with our long-term identity public key - byte[] sig = cryptoComponent.sign(SIGNING_LABEL, ourNonce, - author.getPrivateKey()); - - // Calculate a MAC over identity public key, ephemeral public key, - // transport properties and timestamp. - byte[] publicKeyBytes = localState.getRaw(OUR_PUBLIC_KEY); - BdfDictionary tp = localState.getDictionary(OUR_TRANSPORT); - long ourTime = localState.getLong(OUR_TIME); - BdfList toMacList = BdfList.of(author.getPublicKey(), - publicKeyBytes, tp, ourTime); - byte[] toMac = clientHelper.toByteArray(toMacList); - byte[] mac = cryptoComponent.mac(MAC_LABEL, ourMacKey, toMac); - - // Add MAC and signature to localState, so it can be included in ACK - localState.put(OUR_MAC, mac); - localState.put(OUR_SIGNATURE, sig); - } - - void verifySignature(BdfDictionary localState) - throws FormatException, GeneralSecurityException { - byte[] nonce = localState.getRaw(NONCE); - byte[] sig = localState.getRaw(SIGNATURE); - byte[] key = localState.getRaw(PUBLIC_KEY); - - // Verify the signature - if (!cryptoComponent.verifySignature(sig, SIGNING_LABEL, nonce, key)) { - LOG.warning("Invalid nonce signature in ACK"); - throw new GeneralSecurityException(); - } - } - - void verifyMac(BdfDictionary localState) - throws FormatException, GeneralSecurityException { - // get MAC and MAC key from session state - byte[] mac = localState.getRaw(MAC); - byte[] macKeyBytes = localState.getRaw(MAC_KEY); - SecretKey macKey = new SecretKey(macKeyBytes); - - // get MAC data and calculate a new MAC with stored key - byte[] pubKey = localState.getRaw(PUBLIC_KEY); - byte[] ePubKey = localState.getRaw(E_PUBLIC_KEY); - BdfDictionary tp = localState.getDictionary(TRANSPORT); - long timestamp = localState.getLong(TIME); - BdfList toMacList = BdfList.of(pubKey, ePubKey, tp, timestamp); - byte[] toMac = clientHelper.toByteArray(toMacList); - byte[] calculatedMac = cryptoComponent.mac(MAC_LABEL, macKey, toMac); - if (!Arrays.equals(mac, calculatedMac)) { - LOG.warning("Received ACK with invalid MAC"); - throw new GeneralSecurityException(); - } - } - - public void abort(Transaction txn, BdfDictionary state) { - IntroduceeEngine engine = new IntroduceeEngine(); - BdfDictionary localAction = new BdfDictionary(); - localAction.put(TYPE, TYPE_ABORT); - try { - processStateUpdate(txn, null, - engine.onLocalAction(state, localAction)); - } catch (DbException | IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } - } - - private BdfDictionary encodeTransportProperties( - Map map) { - - BdfDictionary d = new BdfDictionary(); - for (Map.Entry e : map.entrySet()) { - d.put(e.getKey().getString(), e.getValue()); - } - return d; - } - - private Map parseTransportProperties( - BdfDictionary d) throws FormatException { - - Map tpMap = new HashMap<>(); - BdfDictionary tpMapDict = d.getDictionary(TRANSPORT); - for (String key : tpMapDict.keySet()) { - TransportId transportId = new TransportId(key); - TransportProperties transportProperties = new TransportProperties(); - BdfDictionary tpDict = tpMapDict.getDictionary(key); - for (String tkey : tpDict.keySet()) { - transportProperties.put(tkey, tpDict.getString(tkey)); - } - tpMap.put(transportId, transportProperties); - } - return tpMap; - } - -} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java similarity index 89% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeProtocolEngine.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index 1efb6919b..e463dd39a 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; @@ -28,11 +28,12 @@ import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.api.introduction2.IntroductionRequest; -import org.briarproject.briar.api.introduction2.IntroductionResponse; -import org.briarproject.briar.api.introduction2.event.IntroductionRequestReceivedEvent; -import org.briarproject.briar.api.introduction2.event.IntroductionResponseReceivedEvent; -import org.briarproject.briar.api.introduction2.event.IntroductionSucceededEvent; +import org.briarproject.briar.api.introduction.IntroductionRequest; +import org.briarproject.briar.api.introduction.IntroductionResponse; +import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; +import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent; +import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent; +import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent; import java.security.GeneralSecurityException; import java.util.Map; @@ -41,11 +42,11 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; -import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_AUTH; -import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_RESPONSES; -import static org.briarproject.briar.introduction2.IntroduceeState.LOCAL_ACCEPTED; -import static org.briarproject.briar.introduction2.IntroduceeState.REMOTE_ACCEPTED; +import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; +import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH; +import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES; +import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED; +import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_ACCEPTED; @Immutable @NotNullByDefault @@ -145,10 +146,11 @@ class IntroduceeProtocolEngine IntroduceeSession session, AcceptMessage m) throws DbException, FormatException { switch (session.getState()) { + case START: + return onRemoteResponseInStart(txn, session, m); case AWAIT_RESPONSES: case LOCAL_ACCEPTED: return onRemoteAccept(txn, session, m); - case START: case LOCAL_DECLINED: case REMOTE_ACCEPTED: case AWAIT_AUTH: @@ -165,7 +167,7 @@ class IntroduceeProtocolEngine throws DbException, FormatException { switch (session.getState()) { case START: - return session; // Ignore in the START state + return onRemoteResponseInStart(txn, session, m); case AWAIT_RESPONSES: case LOCAL_DECLINED: case LOCAL_ACCEPTED: @@ -322,18 +324,6 @@ class IntroduceeProtocolEngine if (isInvalidDependency(s, m.getPreviousMessageId())) return abort(txn, s); - // Broadcast IntroductionResponseReceivedEvent - Contact c = contactManager.getContact(s.getIntroducer().getId(), - identityManager.getLocalAuthor(txn).getId()); - IntroductionResponse request = - new IntroductionResponse(s.getSessionId(), m.getMessageId(), - m.getGroupId(), INTRODUCEE, m.getTimestamp(), false, - false, false, false, s.getRemoteAuthor().getName(), - true); - IntroductionResponseReceivedEvent e = - new IntroductionResponseReceivedEvent(c.getId(), request); - txn.attach(e); - // Determine next state IntroduceeState state = s.getState() == AWAIT_RESPONSES ? REMOTE_ACCEPTED : AWAIT_AUTH; @@ -356,12 +346,46 @@ class IntroduceeProtocolEngine if (isInvalidDependency(s, m.getPreviousMessageId())) return abort(txn, s); + // Mark the request visible in the UI + markMessageVisibleInUi(txn, m.getMessageId()); + + // Track the incoming message + messageTracker + .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); + + // Broadcast IntroductionResponseReceivedEvent + Contact c = contactManager.getContact(txn, s.getIntroducer().getId(), + identityManager.getLocalAuthor(txn).getId()); + IntroductionResponse request = + new IntroductionResponse(s.getSessionId(), m.getMessageId(), + m.getGroupId(), INTRODUCEE, m.getTimestamp(), false, + false, false, false, s.getRemoteAuthor().getName(), + false); + IntroductionResponseReceivedEvent e = + new IntroductionResponseReceivedEvent(c.getId(), request); + txn.attach(e); + // Move back to START state return IntroduceeSession .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), m.getMessageId()); } + private IntroduceeSession onRemoteResponseInStart(Transaction txn, + IntroduceeSession s, AbstractIntroductionMessage m) throws DbException { + // The timestamp must be higher than the last request message + if (m.getTimestamp() <= s.getRequestTimestamp()) + return abort(txn, s); + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getPreviousMessageId())) + return abort(txn, s); + + // Stay in START state + return IntroduceeSession + .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), + m.getMessageId()); + } + private IntroduceeSession onLocalAuth(Transaction txn, IntroduceeSession s) throws DbException { boolean alice = isAlice(txn, s); @@ -459,6 +483,9 @@ class IntroduceeProtocolEngine if (requestId == null) throw new IllegalStateException(); markRequestUnavailableToAnswer(txn, requestId); + // Broadcast abort event for testing + txn.attach(new IntroductionAbortedEvent(s.getSessionId())); + // Reset the session back to initial state return IntroduceeSession .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), @@ -475,6 +502,9 @@ class IntroduceeProtocolEngine // 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, sent.getId(), sent.getTimestamp(), s.getLastRemoteMessageId()); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java similarity index 95% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeSession.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java index 61e301498..1b44452d9 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.identity.Author; @@ -10,16 +10,16 @@ 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.introduction2.Role; +import org.briarproject.briar.api.introduction.Role; import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_ACTIVATE; -import static org.briarproject.briar.introduction2.IntroduceeState.START; -import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; +import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_ACTIVATE; +import static org.briarproject.briar.introduction.IntroduceeState.START; +import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; @Immutable @NotNullByDefault diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeState.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeState.java similarity index 93% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeState.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeState.java index 4a901752d..7a54abde8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroduceeState.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeState.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerEngine.java deleted file mode 100644 index df364b34f..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerEngine.java +++ /dev/null @@ -1,370 +0,0 @@ -package org.briarproject.briar.introduction; - -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.data.BdfDictionary; -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.identity.AuthorId; -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.ProtocolEngine; -import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.api.introduction.IntroducerAction; -import org.briarproject.briar.api.introduction.IntroducerProtocolState; -import org.briarproject.briar.api.introduction.IntroductionResponse; -import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; -import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.logging.Logger; - -import javax.annotation.concurrent.Immutable; - -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.WARNING; -import static org.briarproject.briar.api.introduction.IntroducerAction.LOCAL_ABORT; -import static org.briarproject.briar.api.introduction.IntroducerAction.LOCAL_REQUEST; -import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_1; -import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_2; -import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_1; -import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_2; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_ACKS; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_ACK_1; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_ACK_2; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSE_1; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSE_2; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.ERROR; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.FINISHED; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; - -@Immutable -@NotNullByDefault -class IntroducerEngine - implements ProtocolEngine { - - private static final Logger LOG = - Logger.getLogger(IntroducerEngine.class.getName()); - - @Override - public StateUpdate onLocalAction( - BdfDictionary localState, BdfDictionary localAction) { - - try { - IntroducerProtocolState currentState = - getState(localState.getLong(STATE)); - int type = localAction.getLong(TYPE).intValue(); - IntroducerAction action = IntroducerAction.getLocal(type); - IntroducerProtocolState nextState = currentState.next(action); - - if (action == LOCAL_ABORT && currentState != ERROR) { - return abortSession(currentState, localState); - } - - if (nextState == ERROR) { - if (LOG.isLoggable(WARNING)) { - LOG.warning("Error: Invalid action in state " + - currentState.name()); - } - return noUpdate(localState); - } - - localState.put(STATE, nextState.getValue()); - if (action == LOCAL_REQUEST) { - // create the introduction requests for both contacts - List messages = new ArrayList<>(2); - BdfDictionary msg1 = new BdfDictionary(); - msg1.put(TYPE, TYPE_REQUEST); - msg1.put(SESSION_ID, localState.getRaw(SESSION_ID)); - msg1.put(GROUP_ID, localState.getRaw(GROUP_ID_1)); - msg1.put(NAME, localState.getString(CONTACT_2)); - msg1.put(PUBLIC_KEY, localAction.getRaw(PUBLIC_KEY2)); - if (localAction.containsKey(MSG)) { - msg1.put(MSG, localAction.getString(MSG)); - } - msg1.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME)); - messages.add(msg1); - logLocalAction(currentState, localState); - BdfDictionary msg2 = new BdfDictionary(); - msg2.put(TYPE, TYPE_REQUEST); - msg2.put(SESSION_ID, localState.getRaw(SESSION_ID)); - msg2.put(GROUP_ID, localState.getRaw(GROUP_ID_2)); - msg2.put(NAME, localState.getString(CONTACT_1)); - msg2.put(PUBLIC_KEY, localAction.getRaw(PUBLIC_KEY1)); - if (localAction.containsKey(MSG)) { - msg2.put(MSG, localAction.getString(MSG)); - } - msg2.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME)); - messages.add(msg2); - logLocalAction(currentState, localState); - - List events = Collections.emptyList(); - return new StateUpdate<>(false, false, - localState, messages, events); - } else { - throw new IllegalArgumentException("Unknown Local Action"); - } - } catch (FormatException e) { - throw new IllegalArgumentException(e); - } - } - - @Override - public StateUpdate onMessageReceived( - BdfDictionary localState, BdfDictionary msg) { - - try { - IntroducerProtocolState currentState = - getState(localState.getLong(STATE)); - int type = msg.getLong(TYPE).intValue(); - boolean one = isContact1(localState, msg); - IntroducerAction action = IntroducerAction.getRemote(type, one); - IntroducerProtocolState nextState = currentState.next(action); - - logMessageReceived(currentState, nextState, localState, type, msg); - - if (nextState == ERROR) { - if (currentState != ERROR) { - return abortSession(currentState, localState); - } else { - return noUpdate(localState); - } - } - - List messages; - List events; - - // we have sent our requests and just got the 1st or 2nd response - if (currentState == AWAIT_RESPONSES || - currentState == AWAIT_RESPONSE_1 || - currentState == AWAIT_RESPONSE_2) { - // update next state based on message content - action = IntroducerAction - .getRemote(type, one, msg.getBoolean(ACCEPT)); - nextState = currentState.next(action); - localState.put(STATE, nextState.getValue()); - if (one) localState.put(RESPONSE_1, msg.getRaw(MESSAGE_ID)); - else localState.put(RESPONSE_2, msg.getRaw(MESSAGE_ID)); - - messages = forwardMessage(localState, msg); - events = Collections.singletonList(getEvent(localState, msg)); - } - // we have forwarded both responses and now received the 1st or 2nd ACK - else if (currentState == AWAIT_ACKS || - currentState == AWAIT_ACK_1 || - currentState == AWAIT_ACK_2) { - localState.put(STATE, nextState.getValue()); - messages = forwardMessage(localState, msg); - events = Collections.emptyList(); - } - // we probably received a response while already being FINISHED - else if (currentState == FINISHED) { - // if it was a response store it to be found later - if (action == REMOTE_ACCEPT_1 || action == REMOTE_DECLINE_1) { - localState.put(RESPONSE_1, msg.getRaw(MESSAGE_ID)); - messages = Collections.emptyList(); - events = Collections - .singletonList(getEvent(localState, msg)); - } else if (action == REMOTE_ACCEPT_2 || - action == REMOTE_DECLINE_2) { - localState.put(RESPONSE_2, msg.getRaw(MESSAGE_ID)); - messages = Collections.emptyList(); - events = Collections - .singletonList(getEvent(localState, msg)); - } else return noUpdate(localState); - } else { - throw new IllegalArgumentException("Bad state"); - } - return new StateUpdate<>(false, false, - localState, messages, events); - } catch (FormatException e) { - throw new IllegalArgumentException(e); - } - } - - private void logLocalAction(IntroducerProtocolState state, - BdfDictionary localState) { - - if (!LOG.isLoggable(INFO)) return; - try { - LOG.info("Sending introduction request in state " + state.name()); - LOG.info("Moving on to state " + - getState(localState.getLong(STATE)).name()); - } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } - } - - private void logMessageReceived(IntroducerProtocolState currentState, - IntroducerProtocolState nextState, - BdfDictionary localState, int type, BdfDictionary msg) { - - if (!LOG.isLoggable(INFO)) return; - - String t = "unknown"; - if (type == TYPE_REQUEST) t = "Introduction"; - else if (type == TYPE_RESPONSE) t = "Response"; - else if (type == TYPE_ACK) t = "ACK"; - else if (type == TYPE_ABORT) t = "Abort"; - - LOG.info("Received " + t + " in state " + currentState.name()); - LOG.info("Moving on to state " + nextState.name()); - } - - private List forwardMessage(BdfDictionary localState, - BdfDictionary message) throws FormatException { - - // clone the message here, because we still need the original - BdfDictionary msg = (BdfDictionary) message.clone(); - if (isContact1(localState, msg)) { - msg.put(GROUP_ID, localState.getRaw(GROUP_ID_2)); - } else { - msg.put(GROUP_ID, localState.getRaw(GROUP_ID_1)); - } - - return Collections.singletonList(msg); - } - - @Override - public StateUpdate onMessageDelivered( - BdfDictionary localState, BdfDictionary delivered) { - try { - return noUpdate(localState); - } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - return null; - } - } - - private IntroducerProtocolState getState(Long state) { - return IntroducerProtocolState.fromValue(state.intValue()); - } - - private Event getEvent(BdfDictionary localState, BdfDictionary msg) - throws FormatException { - - ContactId contactId = - new ContactId(localState.getLong(CONTACT_ID_1).intValue()); - AuthorId authorId = new AuthorId(localState.getRaw(AUTHOR_ID_1)); - if (Arrays - .equals(msg.getRaw(GROUP_ID), localState.getRaw(GROUP_ID_2))) { - contactId = - new ContactId(localState.getLong(CONTACT_ID_2).intValue()); - authorId = new AuthorId(localState.getRaw(AUTHOR_ID_2)); - } - - SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); - MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID)); - GroupId groupId = new GroupId(msg.getRaw(GROUP_ID)); - long time = msg.getLong(MESSAGE_TIME); - String name = getOtherContact(localState, msg); - boolean accept = msg.getBoolean(ACCEPT); - - IntroductionResponse ir = - new IntroductionResponse(sessionId, messageId, groupId, - ROLE_INTRODUCER, time, false, false, false, false, - authorId, name, accept); - return new IntroductionResponseReceivedEvent(contactId, ir); - } - - private boolean isContact1(BdfDictionary localState, BdfDictionary msg) - throws FormatException { - - byte[] group = msg.getRaw(GROUP_ID); - byte[] group1 = localState.getRaw(GROUP_ID_1); - byte[] group2 = localState.getRaw(GROUP_ID_2); - - if (Arrays.equals(group, group1)) { - return true; - } else if (Arrays.equals(group, group2)) { - return false; - } else { - throw new FormatException(); - } - } - - private String getOtherContact(BdfDictionary localState, BdfDictionary msg) - throws FormatException { - - String to = localState.getString(CONTACT_2); - if (Arrays - .equals(msg.getRaw(GROUP_ID), localState.getRaw(GROUP_ID_2))) { - to = localState.getString(CONTACT_1); - } - return to; - } - - private StateUpdate abortSession( - IntroducerProtocolState currentState, BdfDictionary localState) - throws FormatException { - - if (LOG.isLoggable(WARNING)) - LOG.warning("Aborting protocol session in state " + - currentState.name()); - - localState.put(STATE, ERROR.getValue()); - List messages = new ArrayList<>(2); - BdfDictionary msg1 = new BdfDictionary(); - msg1.put(TYPE, TYPE_ABORT); - msg1.put(SESSION_ID, localState.getRaw(SESSION_ID)); - msg1.put(GROUP_ID, localState.getRaw(GROUP_ID_1)); - messages.add(msg1); - BdfDictionary msg2 = new BdfDictionary(); - msg2.put(TYPE, TYPE_ABORT); - msg2.put(SESSION_ID, localState.getRaw(SESSION_ID)); - msg2.put(GROUP_ID, localState.getRaw(GROUP_ID_2)); - messages.add(msg2); - - // send one abort event per contact - List events = new ArrayList<>(2); - SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID)); - ContactId contactId1 = - new ContactId(localState.getLong(CONTACT_ID_1).intValue()); - ContactId contactId2 = - new ContactId(localState.getLong(CONTACT_ID_2).intValue()); - Event event1 = new IntroductionAbortedEvent(contactId1, sessionId); - events.add(event1); - Event event2 = new IntroductionAbortedEvent(contactId2, sessionId); - events.add(event2); - - return new StateUpdate<>(false, false, localState, messages, events); - } - - private StateUpdate noUpdate( - BdfDictionary localState) throws FormatException { - - return new StateUpdate<>(false, false, localState, - Collections.emptyList(), - Collections.emptyList()); - } -} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerManager.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerManager.java deleted file mode 100644 index b24109396..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerManager.java +++ /dev/null @@ -1,181 +0,0 @@ -package org.briarproject.briar.introduction; - -import org.briarproject.bramble.api.Bytes; -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.client.ClientHelper; -import org.briarproject.bramble.api.contact.Contact; -import org.briarproject.bramble.api.crypto.CryptoComponent; -import org.briarproject.bramble.api.data.BdfDictionary; -import org.briarproject.bramble.api.data.BdfList; -import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.db.Transaction; -import org.briarproject.bramble.api.event.Event; -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.bramble.util.StringUtils; - -import java.io.IOException; -import java.util.logging.Logger; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import javax.inject.Inject; - -import static java.util.logging.Level.WARNING; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS; -import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; - -@Immutable -@NotNullByDefault -class IntroducerManager { - - private static final Logger LOG = - Logger.getLogger(IntroducerManager.class.getName()); - - private final MessageSender messageSender; - private final ClientHelper clientHelper; - private final Clock clock; - private final CryptoComponent cryptoComponent; - private final IntroductionGroupFactory introductionGroupFactory; - - @Inject - IntroducerManager(MessageSender messageSender, ClientHelper clientHelper, - Clock clock, CryptoComponent cryptoComponent, - IntroductionGroupFactory introductionGroupFactory) { - - this.messageSender = messageSender; - this.clientHelper = clientHelper; - this.clock = clock; - this.cryptoComponent = cryptoComponent; - this.introductionGroupFactory = introductionGroupFactory; - } - - public BdfDictionary initialize(Transaction txn, Contact c1, Contact c2) - throws FormatException, DbException { - - // create local message to keep engine state - long now = clock.currentTimeMillis(); - Bytes salt = new Bytes(new byte[64]); - cryptoComponent.getSecureRandom().nextBytes(salt.getBytes()); - - Message m = clientHelper.createMessage( - introductionGroupFactory.createLocalGroup().getId(), now, - BdfList.of(salt)); - MessageId sessionId = m.getId(); - - Group g1 = introductionGroupFactory.createIntroductionGroup(c1); - Group g2 = introductionGroupFactory.createIntroductionGroup(c2); - - BdfDictionary d = new BdfDictionary(); - d.put(SESSION_ID, sessionId); - d.put(STORAGE_ID, sessionId); - d.put(STATE, PREPARE_REQUESTS.getValue()); - d.put(ROLE, ROLE_INTRODUCER); - d.put(GROUP_ID_1, g1.getId()); - d.put(GROUP_ID_2, g2.getId()); - d.put(CONTACT_1, c1.getAuthor().getName()); - d.put(CONTACT_2, c2.getAuthor().getName()); - d.put(CONTACT_ID_1, c1.getId().getInt()); - d.put(CONTACT_ID_2, c2.getId().getInt()); - d.put(AUTHOR_ID_1, c1.getAuthor().getId()); - d.put(AUTHOR_ID_2, c2.getAuthor().getId()); - - // save local state to database - clientHelper.addLocalMessage(txn, m, d, false); - - return d; - } - - void makeIntroduction(Transaction txn, Contact c1, Contact c2, - @Nullable String msg, long timestamp) - throws DbException, FormatException { - - // TODO check for existing session with those contacts? - // deny new introduction under which conditions? - - // initialize engine state - BdfDictionary localState = initialize(txn, c1, c2); - - // define action - BdfDictionary localAction = new BdfDictionary(); - localAction.put(TYPE, TYPE_REQUEST); - if (!StringUtils.isNullOrEmpty(msg)) { - int msgLength = StringUtils.toUtf8(msg).length; - if (msgLength > MAX_INTRODUCTION_MESSAGE_LENGTH) - throw new IllegalArgumentException(); - localAction.put(MSG, msg); - } - localAction.put(PUBLIC_KEY1, c1.getAuthor().getPublicKey()); - localAction.put(PUBLIC_KEY2, c2.getAuthor().getPublicKey()); - localAction.put(MESSAGE_TIME, timestamp); - - // start engine and process its state update - IntroducerEngine engine = new IntroducerEngine(); - processStateUpdate(txn, - engine.onLocalAction(localState, localAction)); - } - - public void incomingMessage(Transaction txn, BdfDictionary state, - BdfDictionary message) throws DbException, FormatException { - - IntroducerEngine engine = new IntroducerEngine(); - processStateUpdate(txn, - engine.onMessageReceived(state, message)); - } - - private void processStateUpdate(Transaction txn, - IntroducerEngine.StateUpdate - result) throws DbException, FormatException { - - // save new local state - MessageId storageId = - new MessageId(result.localState.getRaw(STORAGE_ID)); - clientHelper.mergeMessageMetadata(txn, storageId, result.localState); - - // send messages - for (BdfDictionary d : result.toSend) { - messageSender.sendMessage(txn, d); - } - - // broadcast events - for (Event event : result.toBroadcast) { - txn.attach(event); - } - } - - public void abort(Transaction txn, BdfDictionary state) { - IntroducerEngine engine = new IntroducerEngine(); - BdfDictionary localAction = new BdfDictionary(); - localAction.put(TYPE, TYPE_ABORT); - try { - processStateUpdate(txn, - engine.onLocalAction(state, localAction)); - } catch (DbException | IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } - } - -} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java similarity index 78% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerProtocolEngine.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java index a65f18301..044b7ccd2 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; @@ -9,6 +9,7 @@ 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.AuthorId; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; @@ -17,9 +18,10 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; -import org.briarproject.briar.api.introduction2.IntroductionResponse; -import org.briarproject.briar.api.introduction2.event.IntroductionResponseReceivedEvent; -import org.briarproject.briar.introduction2.IntroducerSession.Introducee; +import org.briarproject.briar.api.introduction.IntroductionResponse; +import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; +import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent; +import org.briarproject.briar.introduction.IntroducerSession.Introducee; import java.util.Map; @@ -27,17 +29,17 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; -import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATES; -import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATE_A; -import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATE_B; -import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTHS; -import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTH_A; -import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTH_B; -import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSES; -import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSE_A; -import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSE_B; -import static org.briarproject.briar.introduction2.IntroducerState.START; +import static org.briarproject.briar.api.introduction.Role.INTRODUCER; +import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES; +import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A; +import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B; +import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTHS; +import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTH_A; +import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTH_B; +import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSES; +import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_A; +import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_B; +import static org.briarproject.briar.introduction.IntroducerState.START; @Immutable @NotNullByDefault @@ -94,6 +96,11 @@ class IntroducerProtocolEngine throw new UnsupportedOperationException(); // Invalid in this role } + IntroducerSession onAbortAction(Transaction txn, IntroducerSession s) + throws DbException, FormatException { + return abort(txn, s); + } + @Override public IntroducerSession onRequestMessage(Transaction txn, IntroducerSession s, RequestMessage m) @@ -111,8 +118,7 @@ class IntroducerProtocolEngine case AWAIT_RESPONSE_B: return onRemoteAccept(txn, s, m); case START: - // TODO check and update lastRemoteMsgId? - return s; // Ignored in this state + return onRemoteResponseInStart(txn, s, m); case AWAIT_AUTHS: case AWAIT_AUTH_A: case AWAIT_AUTH_B: @@ -135,8 +141,7 @@ class IntroducerProtocolEngine case AWAIT_RESPONSE_B: return onRemoteDecline(txn, s, m); case START: - // TODO check and update lastRemoteMsgId? - return s; // Ignored in this state + return onRemoteResponseInStart(txn, s, m); case AWAIT_AUTHS: case AWAIT_AUTH_A: case AWAIT_AUTH_B: @@ -253,14 +258,16 @@ class IntroducerProtocolEngine if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_A; introducee1 = new Introducee(s.getIntroducee1(), sent); introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); - c = contactManager.getContact(s.getIntroducee2().author.getId(), - identityManager.getLocalAuthor(txn).getId()); + c = contactManager + .getContact(txn, s.getIntroducee2().author.getId(), + identityManager.getLocalAuthor(txn).getId()); } else if (i.equals(s.getIntroducee2())) { if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_B; introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); introducee2 = new Introducee(s.getIntroducee2(), sent); - c = contactManager.getContact(s.getIntroducee1().author.getId(), - identityManager.getLocalAuthor(txn).getId()); + c = contactManager + .getContact(txn, s.getIntroducee1().author.getId(), + identityManager.getLocalAuthor(txn).getId()); } else throw new AssertionError(); // Broadcast IntroductionResponseReceivedEvent @@ -296,22 +303,23 @@ class IntroducerProtocolEngine Introducee i = getOtherIntroducee(s, m.getGroupId()); long timestamp = getLocalTimestamp(s, i); Message sent = sendDeclineMessage(txn, i, timestamp, false); - // Track the message - messageTracker.trackOutgoingMessage(txn, sent); // Move to the START state Introducee introducee1, introducee2; + AuthorId localAuthorId =identityManager.getLocalAuthor(txn).getId(); Contact c; if (i.equals(s.getIntroducee1())) { introducee1 = new Introducee(s.getIntroducee1(), sent); introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); - c = contactManager.getContact(s.getIntroducee2().author.getId(), - identityManager.getLocalAuthor(txn).getId()); + c = contactManager + .getContact(txn, s.getIntroducee2().author.getId(), + localAuthorId); } else if (i.equals(s.getIntroducee2())) { introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); introducee2 = new Introducee(s.getIntroducee2(), sent); - c = contactManager.getContact(s.getIntroducee2().author.getId(), - identityManager.getLocalAuthor(txn).getId()); + c = contactManager + .getContact(txn, s.getIntroducee1().author.getId(), + localAuthorId); } else throw new AssertionError(); // Broadcast IntroductionResponseReceivedEvent @@ -327,6 +335,54 @@ class IntroducerProtocolEngine s.getRequestTimestamp(), introducee1, introducee2); } + private IntroducerSession onRemoteResponseInStart(Transaction txn, + IntroducerSession s, AbstractIntroductionMessage m) + throws DbException, FormatException { + // The timestamp must be higher than the last request message + if (m.getTimestamp() <= s.getRequestTimestamp()) + return abort(txn, s); + // The dependency, if any, must be the last remote message + if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) + return abort(txn, s); + + // Mark the response visible in the UI + markMessageVisibleInUi(txn, m.getMessageId()); + // Track the incoming message + messageTracker + .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); + + Introducee i = getIntroducee(s, m.getGroupId()); + Introducee introducee1, introducee2; + AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId(); + Contact c; + if (i.equals(s.getIntroducee1())) { + introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); + introducee2 = s.getIntroducee2(); + c = contactManager + .getContact(txn, s.getIntroducee1().author.getId(), + localAuthorId); + } else if (i.equals(s.getIntroducee2())) { + introducee1 = s.getIntroducee1(); + introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); + c = contactManager + .getContact(txn, s.getIntroducee2().author.getId(), + localAuthorId); + } else throw new AssertionError(); + + // Broadcast IntroductionResponseReceivedEvent + IntroductionResponse request = + new IntroductionResponse(s.getSessionId(), m.getMessageId(), + m.getGroupId(), INTRODUCER, m.getTimestamp(), false, + false, false, false, c.getAuthor().getName(), + m instanceof AcceptMessage); + IntroductionResponseReceivedEvent e = + new IntroductionResponseReceivedEvent(c.getId(), request); + txn.attach(e); + + return new IntroducerSession(s.getSessionId(), START, + s.getRequestTimestamp(), introducee1, introducee2); + } + private IntroducerSession onRemoteAuth(Transaction txn, IntroducerSession s, AuthMessage m) throws DbException, FormatException { @@ -395,6 +451,9 @@ class IntroducerProtocolEngine long timestamp = getLocalTimestamp(s, i); Message sent = sendAbortMessage(txn, i, timestamp); + // Broadcast abort event for testing + txn.attach(new IntroductionAbortedEvent(s.getSessionId())); + // Reset the session back to initial state Introducee introducee1, introducee2; if (i.equals(s.getIntroducee1())) { @@ -412,6 +471,10 @@ class IntroducerProtocolEngine IntroducerSession s) throws DbException, FormatException { // Mark any REQUEST messages in the session unavailable to answer markRequestsUnavailableToAnswer(txn, s); + + // Broadcast abort event for testing + txn.attach(new IntroductionAbortedEvent(s.getSessionId())); + // Send an ABORT message to both introducees long timestamp1 = getLocalTimestamp(s, s.getIntroducee1()); Message sent1 = sendAbortMessage(txn, s.getIntroducee1(), timestamp1); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerSession.java similarity index 94% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerSession.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerSession.java index cbfc53923..906947969 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerSession.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -6,12 +6,12 @@ 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 org.briarproject.briar.api.introduction2.Role; +import org.briarproject.briar.api.introduction.Role; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; +import static org.briarproject.briar.api.introduction.Role.INTRODUCER; @Immutable @NotNullByDefault diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerState.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java similarity index 94% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerState.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java index 43e51d96a..99c3fbf86 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroducerState.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java similarity index 97% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java index 634c0fff1..bda50f47b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionConstants.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; interface IntroductionConstants { diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java similarity index 98% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java index a91184db2..a563e3c75 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.crypto.KeyPair; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java similarity index 89% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java index 93ff9b33e..9bf4e46c2 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.FormatException; @@ -24,14 +24,14 @@ import java.util.Map; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_ALICE_MAC_KEY; -import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_MAC; -import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_NONCE; -import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_SIGN; -import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_BOB_MAC_KEY; -import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_MASTER_KEY; -import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID; -import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION; +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.CLIENT_VERSION; @Immutable @NotNullByDefault diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionGroupFactory.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionGroupFactory.java deleted file mode 100644 index 050d2b9f4..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionGroupFactory.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.briarproject.briar.introduction; - -import org.briarproject.bramble.api.client.ContactGroupFactory; -import org.briarproject.bramble.api.contact.Contact; -import org.briarproject.bramble.api.sync.Group; - -import javax.inject.Inject; - -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION; - -class IntroductionGroupFactory { - - private final ContactGroupFactory contactGroupFactory; - private final Group localGroup; - - @Inject - IntroductionGroupFactory(ContactGroupFactory contactGroupFactory) { - this.contactGroupFactory = contactGroupFactory; - localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, - CLIENT_VERSION); - } - - Group createIntroductionGroup(Contact c) { - return contactGroupFactory.createContactGroup(CLIENT_ID, - CLIENT_VERSION, c); - } - - Group createLocalGroup() { - return localGroup; - } - -} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index 9ccf06b67..7b7f9cd9b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -2,19 +2,20 @@ package org.briarproject.briar.introduction; 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.ContactHook; 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.data.MetadataParser; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.db.NoSuchContactException; -import org.briarproject.bramble.api.db.NoSuchMessageException; +import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Transaction; -import org.briarproject.bramble.api.identity.AuthorId; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.Client; import org.briarproject.bramble.api.sync.Group; @@ -24,264 +25,278 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.api.introduction.IntroducerProtocolState; import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.introduction.IntroductionMessage; import org.briarproject.briar.api.introduction.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionResponse; +import org.briarproject.briar.api.introduction.Role; import org.briarproject.briar.client.ConversationClientImpl; +import org.briarproject.briar.introduction.IntroducerSession.Introducee; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; +import java.util.List; import java.util.Map; -import java.util.logging.Logger; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; -import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.FINISHED; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED; -import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US; -import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; -import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; +import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; +import static org.briarproject.briar.api.introduction.Role.INTRODUCER; +import static org.briarproject.briar.introduction.IntroductionConstants.GROUP_KEY_CONTACT_ID; +import static org.briarproject.briar.introduction.MessageType.ABORT; +import static org.briarproject.briar.introduction.MessageType.ACCEPT; +import static org.briarproject.briar.introduction.MessageType.ACTIVATE; +import static org.briarproject.briar.introduction.MessageType.AUTH; +import static org.briarproject.briar.introduction.MessageType.DECLINE; +import static org.briarproject.briar.introduction.MessageType.REQUEST; @Immutable @NotNullByDefault class IntroductionManagerImpl extends ConversationClientImpl implements IntroductionManager, Client, ContactHook { - private static final Logger LOG = - Logger.getLogger(IntroductionManagerImpl.class.getName()); - - private final IntroducerManager introducerManager; - private final IntroduceeManager introduceeManager; - private final IntroductionGroupFactory introductionGroupFactory; + private final ContactGroupFactory contactGroupFactory; + private final MessageParser messageParser; + private final SessionEncoder sessionEncoder; + private final SessionParser sessionParser; + private final IntroducerProtocolEngine introducerEngine; + private final IntroduceeProtocolEngine introduceeEngine; + private final IntroductionCrypto crypto; + private final IdentityManager identityManager; @Inject - IntroductionManagerImpl(DatabaseComponent db, ClientHelper clientHelper, - MetadataParser metadataParser, MessageTracker messageTracker, - IntroducerManager introducerManager, - IntroduceeManager introduceeManager, - IntroductionGroupFactory introductionGroupFactory) { - + IntroductionManagerImpl( + DatabaseComponent db, + ClientHelper clientHelper, + MetadataParser metadataParser, + MessageTracker messageTracker, + ContactGroupFactory contactGroupFactory, + MessageParser messageParser, + SessionEncoder sessionEncoder, + SessionParser sessionParser, + IntroducerProtocolEngine introducerEngine, + IntroduceeProtocolEngine introduceeEngine, + IntroductionCrypto crypto, + IdentityManager identityManager) { super(db, clientHelper, metadataParser, messageTracker); - this.introducerManager = introducerManager; - this.introduceeManager = introduceeManager; - this.introductionGroupFactory = introductionGroupFactory; + this.contactGroupFactory = contactGroupFactory; + this.messageParser = messageParser; + this.sessionEncoder = sessionEncoder; + this.sessionParser = sessionParser; + this.introducerEngine = introducerEngine; + this.introduceeEngine = introduceeEngine; + this.crypto = crypto; + this.identityManager = identityManager; } @Override public void createLocalState(Transaction txn) throws DbException { - Group localGroup = introductionGroupFactory.createLocalGroup(); + // Create a local group to store protocol sessions + Group localGroup = getLocalGroup(); if (db.containsGroup(txn, localGroup.getId())) return; db.addGroup(txn, localGroup); - // Ensure we've set things up for any pre-existing contacts + // Set up groups for communication with any pre-existing contacts for (Contact c : db.getContacts(txn)) addingContact(txn, c); } @Override + // TODO adapt to use upcoming ClientVersioning client public void addingContact(Transaction txn, Contact c) throws DbException { + // Create a group to share with the contact + Group g = getContactGroup(c); + // Return if we've already set things up for this contact + if (db.containsGroup(txn, g.getId())) return; + // Store the group and share it with the contact + db.addGroup(txn, g); + db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); + // Attach the contact ID to the group + BdfDictionary meta = new BdfDictionary(); + meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt()); try { - // Create an introduction group for sending introduction messages - Group g = getContactGroup(c); - // Return if we've already set things up for this contact - if (db.containsGroup(txn, g.getId())) return; - // Store the group and share it with the contact - db.addGroup(txn, g); - db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); - // Attach the contact ID to the group - BdfDictionary gm = new BdfDictionary(); - gm.put(CONTACT, c.getId().getInt()); - clientHelper.mergeGroupMetadata(txn, g.getId(), gm); + clientHelper.mergeGroupMetadata(txn, g.getId(), meta); } catch (FormatException e) { - throw new RuntimeException(e); + throw new AssertionError(e); } } @Override public void removingContact(Transaction txn, Contact c) throws DbException { - GroupId gId = introductionGroupFactory.createLocalGroup().getId(); - - // search for session states where c introduced us - BdfDictionary query = BdfDictionary.of( - new BdfEntry(ROLE, ROLE_INTRODUCEE), - new BdfEntry(CONTACT_ID_1, c.getId().getInt()) - ); try { - Map map = clientHelper - .getMessageMetadataAsDictionary(txn, gId, query); - for (Map.Entry entry : map.entrySet()) { - // delete states if introducee removes introducer - deleteMessage(txn, entry.getKey()); - } + removeSessionWithIntroducer(txn, c); + abortOrRemoveSessionWithIntroducee(txn, c); } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + throw new AssertionError(); } - - // check for open sessions with c and abort those, - // so the other introducee knows - query = BdfDictionary.of( - new BdfEntry(ROLE, ROLE_INTRODUCER) - ); - try { - Map map = clientHelper - .getMessageMetadataAsDictionary(txn, gId, query); - for (Map.Entry entry : map.entrySet()) { - BdfDictionary d = entry.getValue(); - ContactId c1 = new ContactId(d.getLong(CONTACT_ID_1).intValue()); - ContactId c2 = new ContactId(d.getLong(CONTACT_ID_2).intValue()); - - if (c1.equals(c.getId()) || c2.equals(c.getId())) { - IntroducerProtocolState state = IntroducerProtocolState - .fromValue(d.getLong(STATE).intValue()); - // abort protocol if still ongoing - if (IntroducerProtocolState.isOngoing(state)) { - introducerManager.abort(txn, d); - } - // also delete state if both contacts have been deleted - if (c1.equals(c.getId())) { - try { - db.getContact(txn, c2); - } catch (NoSuchContactException e) { - deleteMessage(txn, entry.getKey()); - } - } else if (c2.equals(c.getId())) { - try { - db.getContact(txn, c1); - } catch (NoSuchContactException e) { - deleteMessage(txn, entry.getKey()); - } - } - } - } - } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } - - // remove the group (all messages will be removed with it) - // this contact won't get our abort message, but the other will + // Remove the contact group (all messages will be removed with it) db.removeGroup(txn, getContactGroup(c)); } - /** - * This is called when a new message arrived and is being validated. - * It is the central method where we determine which role we play - * in the introduction protocol and which engine we need to start. - */ @Override - protected boolean incomingMessage(Transaction txn, Message m, BdfList body, - BdfDictionary message) throws DbException, FormatException { - - // Get message data and type - GroupId groupId = m.getGroupId(); - long type = message.getLong(TYPE, -1L); - - // we are an introducee, need to initialize new state - if (type == TYPE_REQUEST) { - boolean stateExists = true; - try { - getSessionState(txn, groupId, message.getRaw(SESSION_ID), false); - } catch (FormatException e) { - stateExists = false; - } - if (stateExists) throw new FormatException(); - BdfDictionary state = - introduceeManager.initialize(txn, groupId, message); - try { - introduceeManager.incomingMessage(txn, state, message); - messageTracker.trackIncomingMessage(txn, m); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - introduceeManager.abort(txn, state); - } catch (FormatException e) { - // FIXME necessary? - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - introduceeManager.abort(txn, state); - } - } - // our role can be anything - else if (type == TYPE_RESPONSE || type == TYPE_ACK || type == TYPE_ABORT) { - BdfDictionary state = - getSessionState(txn, groupId, message.getRaw(SESSION_ID)); - - long role = state.getLong(ROLE, -1L); - try { - if (role == ROLE_INTRODUCER) { - introducerManager.incomingMessage(txn, state, message); - if (type == TYPE_RESPONSE) - messageTracker.trackIncomingMessage(txn, m); - } else if (role == ROLE_INTRODUCEE) { - introduceeManager.incomingMessage(txn, state, message); - if (type == TYPE_RESPONSE && !message.getBoolean(ACCEPT)) - messageTracker.trackIncomingMessage(txn, m); - } else { - if (LOG.isLoggable(WARNING)) - LOG.warning("Unknown role '" + role + "'"); - throw new DbException(); - } - } catch (DbException | FormatException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - if (role == ROLE_INTRODUCER) introducerManager.abort(txn, state); - else introduceeManager.abort(txn, state); - } - } else { - // the message has been validated, so this should not happen - if(LOG.isLoggable(WARNING)) { - LOG.warning("Unknown message type '" + type + "', deleting..."); - } - } - return false; + public Group getContactGroup(Contact c) { + return contactGroupFactory + .createContactGroup(CLIENT_ID, CLIENT_VERSION, c); } @Override - public Group getContactGroup(Contact contact) { - return introductionGroupFactory.createIntroductionGroup(contact); + protected boolean incomingMessage(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 newIntroduceeSession = null; + if (sessionId == null) { + if (meta.getMessageType() != REQUEST) throw new AssertionError(); + newIntroduceeSession = createNewIntroduceeSession(txn, m, body); + sessionId = newIntroduceeSession.getSessionId(); + } + StoredSession ss = getSession(txn, sessionId); + // Handle the message + Session session; + MessageId storageId; + if (ss == null) { + if (meta.getMessageType() != REQUEST) throw new FormatException(); + if (newIntroduceeSession == null) throw new AssertionError(); + storageId = createStorageId(txn); + session = handleMessage(txn, m, body, meta.getMessageType(), + newIntroduceeSession, introduceeEngine); + } else { + storageId = ss.storageId; + Role role = sessionParser.getRole(ss.bdfSession); + if (role == INTRODUCER) { + session = handleMessage(txn, m, body, meta.getMessageType(), + sessionParser.parseIntroducerSession(ss.bdfSession), + introducerEngine); + } else if (role == INTRODUCEE) { + session = handleMessage(txn, m, body, meta.getMessageType(), + sessionParser.parseIntroduceeSession(m.getGroupId(), + ss.bdfSession), introduceeEngine); + } else throw new AssertionError(); + } + // Store the updated session + storeSession(txn, storageId, session); + return false; + } + + private IntroduceeSession createNewIntroduceeSession(Transaction txn, + Message m, BdfList body) throws DbException, FormatException { + ContactId introducerId = getContactId(txn, m.getGroupId()); + Author introducer = db.getContact(txn, introducerId).getAuthor(); + Author alice = identityManager.getLocalAuthor(txn); + Author bob = messageParser.parseRequestMessage(m, body).getAuthor(); + if (alice.equals(bob)) throw new FormatException(); + SessionId sessionId = crypto.getSessionId(introducer, alice, bob); + return IntroduceeSession + .getInitial(m.getGroupId(), sessionId, introducer, bob); + } + + private S handleMessage(Transaction txn, Message m, + BdfList body, MessageType type, S session, ProtocolEngine engine) + throws DbException, FormatException { + if (type == REQUEST) { + RequestMessage request = messageParser.parseRequestMessage(m, body); + return engine.onRequestMessage(txn, session, request); + } else if (type == ACCEPT) { + AcceptMessage accept = messageParser.parseAcceptMessage(m, body); + return engine.onAcceptMessage(txn, session, accept); + } else if (type == DECLINE) { + DeclineMessage decline = messageParser.parseDeclineMessage(m, body); + return engine.onDeclineMessage(txn, session, decline); + } else if (type == AUTH) { + AuthMessage auth = messageParser.parseAuthMessage(m, body); + return engine.onAuthMessage(txn, session, auth); + } else if (type == ACTIVATE) { + ActivateMessage activate = + messageParser.parseActivateMessage(m, body); + return engine.onActivateMessage(txn, session, activate); + } else if (type == ABORT) { + AbortMessage abort = messageParser.parseAbortMessage(m, body); + return engine.onAbortMessage(txn, session, abort); + } else { + throw new AssertionError(); + } + } + + @Nullable + private StoredSession getSession(Transaction txn, + @Nullable SessionId sessionId) throws DbException, FormatException { + if (sessionId == null) return null; + BdfDictionary query = sessionParser.getSessionQuery(sessionId); + Map results = clientHelper + .getMessageMetadataAsDictionary(txn, getLocalGroup().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()); + } + + 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()); + } + + private MessageId createStorageId(Transaction txn) throws DbException { + Message m = clientHelper + .createMessageForStoringMetadata(getLocalGroup().getId()); + db.addLocalMessage(txn, m, new Metadata(), false); + return m.getId(); + } + + private void storeSession(Transaction txn, MessageId storageId, + Session session) throws DbException, FormatException { + BdfDictionary d; + if (session.getRole() == INTRODUCER) { + d = sessionEncoder + .encodeIntroducerSession((IntroducerSession) session); + } else if (session.getRole() == INTRODUCEE) { + d = sessionEncoder + .encodeIntroduceeSession((IntroduceeSession) session); + } else { + throw new AssertionError(); + } + clientHelper.mergeMessageMetadata(txn, storageId, d); } @Override public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg, - long timestamp) throws DbException, FormatException { - + long timestamp) throws DbException { Transaction txn = db.startTransaction(false); try { - introducerManager.makeIntroduction(txn, c1, c2, msg, timestamp); - Group g1 = getContactGroup(c1); - Group g2 = getContactGroup(c2); - messageTracker.trackMessage(txn, g1.getId(), timestamp, true); - messageTracker.trackMessage(txn, g2.getId(), timestamp, true); + // Look up the session, if there is one + Author introducer = identityManager.getLocalAuthor(txn); + SessionId sessionId = + crypto.getSessionId(introducer, c1.getAuthor(), + c2.getAuthor()); + StoredSession ss = getSession(txn, sessionId); + // Create or parse the session + IntroducerSession session; + MessageId storageId; + if (ss == null) { + // This is the first request - create a new session + GroupId groupId1 = getContactGroup(c1).getId(); + GroupId groupId2 = getContactGroup(c2).getId(); + session = new IntroducerSession(sessionId, groupId1, + c1.getAuthor(), groupId2, c2.getAuthor()); + storageId = createStorageId(txn); + } else { + // An earlier request exists, so we already have a session + session = sessionParser.parseIntroducerSession(ss.bdfSession); + storageId = ss.storageId; + } + // Handle the request action + session = introducerEngine + .onRequestAction(txn, session, msg, timestamp); + // Store the updated session + storeSession(txn, storageId, session); db.commitTransaction(txn); + } catch (FormatException e) { + throw new DbException(e); } finally { db.endTransaction(txn); } @@ -289,147 +304,78 @@ class IntroductionManagerImpl extends ConversationClientImpl @Override public void acceptIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException, FormatException { - - Transaction txn = db.startTransaction(false); - try { - Contact c = db.getContact(txn, contactId); - Group g = getContactGroup(c); - BdfDictionary state = - getSessionState(txn, g.getId(), sessionId.getBytes()); - - introduceeManager.acceptIntroduction(txn, state, timestamp); - messageTracker.trackMessage(txn, g.getId(), timestamp, true); - db.commitTransaction(txn); - } finally { - db.endTransaction(txn); - } + long timestamp) throws DbException { + respondToRequest(contactId, sessionId, timestamp, true); } @Override public void declineIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException, FormatException { + long timestamp) throws DbException { + respondToRequest(contactId, sessionId, timestamp, false); + } + private void respondToRequest(ContactId contactId, SessionId sessionId, + long timestamp, boolean accept) throws DbException { Transaction txn = db.startTransaction(false); try { - Contact c = db.getContact(txn, contactId); - Group g = getContactGroup(c); - BdfDictionary state = - getSessionState(txn, g.getId(), sessionId.getBytes()); - - introduceeManager.declineIntroduction(txn, state, timestamp); - messageTracker.trackMessage(txn, g.getId(), timestamp, true); + // Look up the session + StoredSession ss = getSession(txn, sessionId); + if (ss == null) throw new IllegalArgumentException(); + // Parse the session + Contact contact = db.getContact(txn, contactId); + GroupId contactGroupId = getContactGroup(contact).getId(); + IntroduceeSession session = sessionParser + .parseIntroduceeSession(contactGroupId, ss.bdfSession); + // Handle the join or leave action + if (accept) { + session = introduceeEngine + .onAcceptAction(txn, session, timestamp); + } else { + session = introduceeEngine + .onDeclineAction(txn, session, timestamp); + } + // Store the updated session + storeSession(txn, ss.storageId, session); db.commitTransaction(txn); + } catch (FormatException e) { + throw new DbException(e); } finally { db.endTransaction(txn); } } @Override - public Collection getIntroductionMessages( - ContactId contactId) throws DbException { - - Collection list = new ArrayList<>(); - - Map metadata; - Collection statuses; + public Collection getIntroductionMessages(ContactId c) + throws DbException { + List messages; Transaction txn = db.startTransaction(true); try { - // get messages and their status - GroupId g = getContactGroup(db.getContact(txn, contactId)).getId(); - metadata = clientHelper.getMessageMetadataAsDictionary(txn, g); - statuses = db.getMessageStatus(txn, contactId, g); - - // turn messages into classes for the UI - for (MessageStatus s : statuses) { - MessageId messageId = s.getMessageId(); - BdfDictionary msg = metadata.get(messageId); - if (msg == null) continue; - - try { - long type = msg.getLong(TYPE); - if (type == TYPE_ACK || type == TYPE_ABORT) continue; - - // get session state - SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID)); - BdfDictionary state = - getSessionState(txn, g, sessionId.getBytes()); - - int role = state.getLong(ROLE).intValue(); - boolean local; - long time = msg.getLong(MESSAGE_TIME); - boolean accepted = msg.getBoolean(ACCEPT, false); - boolean read = msg.getBoolean(MSG_KEY_READ, false); - AuthorId authorId; - String name; - if (type == TYPE_RESPONSE) { - if (role == ROLE_INTRODUCER) { - if (!concernsThisContact(contactId, messageId, state)) { - // this response is not from contactId - continue; - } - local = false; - authorId = - getAuthorIdForIntroducer(contactId, state); - name = getNameForIntroducer(contactId, state); - } else { - if (Arrays.equals(state.getRaw(NOT_OUR_RESPONSE), - messageId.getBytes())) { - // this response is not ours, - // check if it was a decline - if (!accepted) { - local = false; - } else { - // don't include positive responses - continue; - } - } else { - local = true; - } - authorId = new AuthorId( - state.getRaw(REMOTE_AUTHOR_ID)); - name = state.getString(NAME); - } - IntroductionResponse ir = new IntroductionResponse( - sessionId, messageId, g, role, time, local, - s.isSent(), s.isSeen(), read, authorId, name, - accepted); - list.add(ir); - } else if (type == TYPE_REQUEST) { - String message; - boolean answered, exists, introducesOtherIdentity; - if (role == ROLE_INTRODUCER) { - local = true; - authorId = - getAuthorIdForIntroducer(contactId, state); - name = getNameForIntroducer(contactId, state); - message = msg.getOptionalString(MSG); - answered = false; - exists = false; - introducesOtherIdentity = false; - } else { - local = false; - authorId = new AuthorId( - state.getRaw(REMOTE_AUTHOR_ID)); - name = state.getString(NAME); - message = state.getOptionalString(MSG); - boolean finished = state.getLong(STATE) == - FINISHED.getValue(); - answered = finished || state.getBoolean(ANSWERED); - exists = state.getBoolean(EXISTS); - introducesOtherIdentity = - state.getBoolean(REMOTE_AUTHOR_IS_US); - } - IntroductionRequest ir = new IntroductionRequest( - sessionId, messageId, g, role, time, local, - s.isSent(), s.isSeen(), read, authorId, name, - accepted, message, answered, exists, - introducesOtherIdentity); - list.add(ir); - } - } catch (FormatException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); + Contact contact = db.getContact(txn, c); + GroupId contactGroupId = getContactGroup(contact).getId(); + BdfDictionary query = messageParser.getMessagesVisibleInUiQuery(); + Map results = clientHelper + .getMessageMetadataAsDictionary(txn, contactGroupId, query); + messages = new ArrayList<>(results.size()); + for (Map.Entry e : results.entrySet()) { + MessageId m = e.getKey(); + MessageMetadata meta = + messageParser.parseMetadata(e.getValue()); + MessageStatus status = db.getMessageStatus(txn, c, m); + StoredSession ss = getSession(txn, meta.getSessionId()); + if (ss == null) throw new AssertionError(); + MessageType type = meta.getMessageType(); + if (type == REQUEST) { + messages.add( + parseInvitationRequest(txn, contactGroupId, m, + meta, status, ss.bdfSession)); + } else if (type == ACCEPT) { + messages.add( + parseInvitationResponse(txn, contactGroupId, m, + meta, status, ss.bdfSession, true)); + } else if (type == DECLINE) { + messages.add( + parseInvitationResponse(txn, contactGroupId, m, + meta, status, ss.bdfSession, false)); } } db.commitTransaction(txn); @@ -438,88 +384,129 @@ class IntroductionManagerImpl extends ConversationClientImpl } finally { db.endTransaction(txn); } - return list; + return messages; } - private String getNameForIntroducer(ContactId contactId, - BdfDictionary state) throws FormatException { - - if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue()) - return state.getString(CONTACT_2); - if (contactId.getInt() == state.getLong(CONTACT_ID_2).intValue()) - return state.getString(CONTACT_1); - throw new RuntimeException( - "Contact not part of this introduction session"); - } - - private AuthorId getAuthorIdForIntroducer(ContactId contactId, - BdfDictionary state) throws FormatException { - - if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue()) - return new AuthorId(state.getRaw(AUTHOR_ID_2)); - if (contactId.getInt() == state.getLong(CONTACT_ID_2).intValue()) - return new AuthorId(state.getRaw(AUTHOR_ID_1)); - throw new RuntimeException( - "Contact not part of this introduction session"); - } - - private boolean concernsThisContact(ContactId contactId, MessageId messageId, - BdfDictionary state) throws FormatException { - - if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue()) { - return Arrays.equals(state.getRaw(RESPONSE_1, new byte[0]), - messageId.getBytes()); - } else { - return Arrays.equals(state.getRaw(RESPONSE_2, new byte[0]), - messageId.getBytes()); - } - } - - private BdfDictionary getSessionState(Transaction txn, GroupId groupId, - byte[] sessionId, boolean warn) + private IntroductionRequest parseInvitationRequest(Transaction txn, + GroupId contactGroupId, MessageId m, MessageMetadata meta, + MessageStatus status, BdfDictionary bdfSession) throws DbException, FormatException { + Role role = sessionParser.getRole(bdfSession); + SessionId sessionId; + Author author; + if (role == INTRODUCER) { + IntroducerSession session = + sessionParser.parseIntroducerSession(bdfSession); + sessionId = session.getSessionId(); + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + if (localAuthor.equals(session.getIntroducee1().author)) { + author = session.getIntroducee2().author; + } else { + author = session.getIntroducee1().author; + } + } else if (role == INTRODUCEE) { + IntroduceeSession session = sessionParser + .parseIntroduceeSession(contactGroupId, bdfSession); + sessionId = session.getSessionId(); + author = session.getRemoteAuthor(); + } else throw new AssertionError(); + String message = ""; // TODO + boolean contactExists = false; // TODO - try { - // See if we can find the state directly for the introducer - BdfDictionary state = clientHelper - .getMessageMetadataAsDictionary(txn, - new MessageId(sessionId)); - GroupId g1 = new GroupId(state.getRaw(GROUP_ID_1)); - GroupId g2 = new GroupId(state.getRaw(GROUP_ID_2)); - if (!g1.equals(groupId) && !g2.equals(groupId)) { - throw new NoSuchMessageException(); + return new IntroductionRequest(sessionId, m, contactGroupId, + role, meta.getTimestamp(), meta.isLocal(), + status.isSent(), status.isSeen(), meta.isRead(), + author.getName(), false, message, !meta.isAvailableToAnswer(), + contactExists); + } + + private IntroductionResponse parseInvitationResponse(Transaction txn, + GroupId contactGroupId, MessageId m, MessageMetadata meta, + MessageStatus status, BdfDictionary bdfSession, boolean accept) + throws FormatException, DbException { + Role role = sessionParser.getRole(bdfSession); + SessionId sessionId; + Author author; + if (role == INTRODUCER) { + IntroducerSession session = + sessionParser.parseIntroducerSession(bdfSession); + sessionId = session.getSessionId(); + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + if (localAuthor.equals(session.getIntroducee1().author)) { + author = session.getIntroducee2().author; + } else { + author = session.getIntroducee1().author; } - return state; - } catch (NoSuchMessageException e) { - // State not found directly, so iterate over all states - // to find state for introducee - Map map = clientHelper - .getMessageMetadataAsDictionary(txn, - introductionGroupFactory.createLocalGroup().getId()); - for (Map.Entry m : map.entrySet()) { - if (Arrays.equals(m.getValue().getRaw(SESSION_ID), sessionId)) { - BdfDictionary state = m.getValue(); - GroupId g = new GroupId(state.getRaw(GROUP_ID)); - if (g.equals(groupId)) return state; - } - } - if (warn && LOG.isLoggable(WARNING)) - LOG.warning("No session state found"); - throw new FormatException(); + } else if (role == INTRODUCEE) { + IntroduceeSession session = sessionParser + .parseIntroduceeSession(contactGroupId, bdfSession); + sessionId = session.getSessionId(); + author = session.getRemoteAuthor(); + } else throw new AssertionError(); + return new IntroductionResponse(sessionId, m, contactGroupId, + role, meta.getTimestamp(), meta.isLocal(), status.isSent(), + status.isSeen(), meta.isRead(), author.getName(), accept); + } + + private void removeSessionWithIntroducer(Transaction txn, + Contact introducer) throws DbException, FormatException { + BdfDictionary query = sessionEncoder + .getIntroduceeSessionsByIntroducerQuery(introducer.getAuthor()); + Map sessions = clientHelper + .getMessageMetadataAsDictionary(txn, getLocalGroup().getId(), + query); + for (MessageId id : sessions.keySet()) { + db.deleteMessageMetadata(txn, id); // TODO needed? + db.removeMessage(txn, id); } } - private BdfDictionary getSessionState(Transaction txn, GroupId groupId, - byte[] sessionId) throws DbException, FormatException { - - return getSessionState(txn, groupId, sessionId, true); + private void abortOrRemoveSessionWithIntroducee(Transaction txn, + Contact c) throws DbException, FormatException { + BdfDictionary query = sessionEncoder.getIntroducerSessionsQuery(); + Map sessions = clientHelper + .getMessageMetadataAsDictionary(txn, getLocalGroup().getId(), + query); + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); + for (Map.Entry session : sessions + .entrySet()) { + IntroducerSession s = + sessionParser.parseIntroducerSession(session.getValue()); + if (s.getIntroducee1().author.equals(c.getAuthor())) { + abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(), + s.getIntroducee2(), localAuthor); + } else if (s.getIntroducee2().author.equals(c.getAuthor())) { + abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(), + s.getIntroducee1(), localAuthor); + } + } } - private void deleteMessage(Transaction txn, MessageId messageId) - throws DbException { + private void abortOrRemoveSessionWithIntroducee(Transaction txn, + IntroducerSession s, MessageId storageId, Introducee i, + LocalAuthor localAuthor) throws DbException, FormatException { + if (db.containsContact(txn, i.author.getId(), localAuthor.getId())) { + IntroducerSession session = introducerEngine.onAbortAction(txn, s); + storeSession(txn, storageId, session); + } else { + db.deleteMessageMetadata(txn, storageId); // TODO needed? + db.removeMessage(txn, storageId); + } + } - db.deleteMessage(txn, messageId); - db.deleteMessageMetadata(txn, messageId); + private Group getLocalGroup() { + return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); + } + + 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/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionModule.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionModule.java index e0122faa9..24c649c5f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionModule.java @@ -4,8 +4,8 @@ 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.briar.api.client.MessageQueueManager; import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.messaging.ConversationManager; @@ -21,22 +21,22 @@ import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT public class IntroductionModule { public static class EagerSingletons { - @Inject - IntroductionManager introductionManager; @Inject IntroductionValidator introductionValidator; + @Inject + IntroductionManager introductionManager; } @Provides @Singleton - IntroductionValidator provideValidator( - MessageQueueManager messageQueueManager, - MetadataEncoder metadataEncoder, ClientHelper clientHelper, - Clock clock) { + IntroductionValidator provideValidator(ValidationManager validationManager, + MessageEncoder messageEncoder, MetadataEncoder metadataEncoder, + ClientHelper clientHelper, Clock clock) { - IntroductionValidator introductionValidator = new IntroductionValidator( - clientHelper, metadataEncoder, clock); - messageQueueManager.registerMessageValidator(CLIENT_ID, + IntroductionValidator introductionValidator = + new IntroductionValidator(messageEncoder, clientHelper, + metadataEncoder, clock); + validationManager.registerMessageValidator(CLIENT_ID, introductionValidator); return introductionValidator; @@ -46,16 +46,42 @@ public class IntroductionModule { @Singleton IntroductionManager provideIntroductionManager( LifecycleManager lifecycleManager, ContactManager contactManager, - MessageQueueManager messageQueueManager, + ValidationManager validationManager, ConversationManager conversationManager, IntroductionManagerImpl introductionManager) { - lifecycleManager.registerClient(introductionManager); contactManager.registerContactHook(introductionManager); - messageQueueManager.registerIncomingMessageHook(CLIENT_ID, + validationManager.registerIncomingMessageHook(CLIENT_ID, introductionManager); conversationManager.registerConversationClient(introductionManager); return introductionManager; } + + @Provides + MessageParser provideMessageParser(MessageParserImpl messageParser) { + return messageParser; + } + + @Provides + MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) { + return messageEncoder; + } + + @Provides + SessionParser provideSessionParser(SessionParserImpl sessionParser) { + return sessionParser; + } + + @Provides + SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) { + return sessionEncoder; + } + + @Provides + IntroductionCrypto provideIntroductionCrypto( + IntroductionCryptoImpl introductionCrypto) { + return introductionCrypto; + } + } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java index 9fc526e49..16d26afdf 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java @@ -1,7 +1,9 @@ package org.briarproject.briar.introduction; 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; @@ -9,183 +11,166 @@ 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 org.briarproject.briar.client.BdfQueueMessageValidator; + +import java.util.Collections; import javax.annotation.concurrent.Immutable; -import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; -import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +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.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; -import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT; -import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkSize; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; +import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; +import static org.briarproject.briar.introduction.MessageType.ACCEPT; +import static org.briarproject.briar.introduction.MessageType.AUTH; + @Immutable @NotNullByDefault -class IntroductionValidator extends BdfQueueMessageValidator { +class IntroductionValidator extends BdfMessageValidator { - IntroductionValidator(ClientHelper clientHelper, - MetadataEncoder metadataEncoder, Clock clock) { + private final MessageEncoder messageEncoder; + + IntroductionValidator(MessageEncoder 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()); - BdfDictionary d; - long type = body.getLong(0); - byte[] id = body.getRaw(1); - checkLength(id, SessionId.LENGTH); + switch (type) { + case REQUEST: + return validateRequestMessage(m, body); + case ACCEPT: + return validateAcceptMessage(m, body); + case AUTH: + return validateAuthMessage(m, body); + case DECLINE: + case ACTIVATE: + case ABORT: + return validateOtherMessage(type, m, body); + default: + throw new FormatException(); + } + } - if (type == TYPE_REQUEST) { - d = validateRequest(body); - } else if (type == TYPE_RESPONSE) { - d = validateResponse(body); - } else if (type == TYPE_ACK) { - d = validateAck(body); - } else if (type == TYPE_ABORT) { - d = validateAbort(body); + 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); + + String msg = body.getOptionalString(3); + checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH); + + BdfDictionary meta = messageEncoder + .encodeRequestMetadata(m.getTimestamp(), false, false, + false, false); + if (previousMessageId == null) { + return new BdfMessageContext(meta); } else { - throw new FormatException(); + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); } - - d.put(TYPE, type); - d.put(SESSION_ID, id); - d.put(GROUP_ID, m.getGroupId()); - d.put(MESSAGE_ID, m.getId()); - d.put(MESSAGE_TIME, m.getTimestamp()); - return new BdfMessageContext(d); } - private BdfDictionary validateRequest(BdfList message) + private BdfMessageContext validateAcceptMessage(Message m, BdfList body) throws FormatException { + checkSize(body, 6); - checkSize(message, 4, 5); + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); - // TODO: Exchange author format version + byte[] previousMessageId = body.getOptionalRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); - // parse contact name - String name = message.getString(2); - checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH); + byte[] ephemeralPublicKey = body.getRaw(3); + checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH); - // parse contact's public key - byte[] key = message.getRaw(3); - checkLength(key, 0, MAX_PUBLIC_KEY_LENGTH); + body.getLong(4); - // parse (optional) message - String msg = null; - if (message.size() == 5) { - msg = message.getString(4); - checkLength(msg, 0, MAX_INTRODUCTION_MESSAGE_LENGTH); + BdfDictionary transportProperties = body.getDictionary(5); + if (transportProperties.size() < 1) throw new FormatException(); + for (String tId : transportProperties.keySet()) { + checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH); + BdfDictionary tProps = transportProperties.getDictionary(tId); + clientHelper.parseAndValidateTransportProperties(tProps); } - // Return the metadata - BdfDictionary d = new BdfDictionary(); - d.put(NAME, name); - d.put(PUBLIC_KEY, key); - if (msg != null) { - d.put(MSG, msg); - } - return d; - } - - private BdfDictionary validateResponse(BdfList message) - throws FormatException { - - checkSize(message, 3, 6); - - // parse accept/decline - boolean accept = message.getBoolean(2); - - long time = 0; - byte[] pubkey = null; - BdfDictionary tp = new BdfDictionary(); - if (accept) { - checkSize(message, 6); - - // parse timestamp - time = message.getLong(3); - - // parse ephemeral public key - pubkey = message.getRaw(4); - checkLength(pubkey, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES); - - // parse transport properties - tp = message.getDictionary(5); - if (tp.size() < 1) throw new FormatException(); - for (String tId : tp.keySet()) { - checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH); - BdfDictionary tProps = tp.getDictionary(tId); - checkSize(tProps, 0, MAX_PROPERTIES_PER_TRANSPORT); - for (String propId : tProps.keySet()) { - checkLength(propId, 0, MAX_PROPERTY_LENGTH); - String prop = tProps.getString(propId); - checkLength(prop, 0, MAX_PROPERTY_LENGTH); - } - } + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(ACCEPT, sessionId, m.getTimestamp(), false, + false, false); + if (previousMessageId == null) { + return new BdfMessageContext(meta); } else { - checkSize(message, 3); + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); } - - // Return the metadata - BdfDictionary d = new BdfDictionary(); - d.put(ACCEPT, accept); - if (accept) { - d.put(TIME, time); - d.put(E_PUBLIC_KEY, pubkey); - d.put(TRANSPORT, tp); - } - return d; } - private BdfDictionary validateAck(BdfList message) throws FormatException { - checkSize(message, 4); - - byte[] mac = message.getRaw(2); - checkLength(mac, 1, MAC_LENGTH); - - byte[] sig = message.getRaw(3); - checkLength(sig, 1, MAX_SIGNATURE_LENGTH); - - // Return the metadata - BdfDictionary d = new BdfDictionary(); - d.put(MAC, mac); - d.put(SIGNATURE, sig); - return d; - } - - private BdfDictionary validateAbort(BdfList message) + private BdfMessageContext validateAuthMessage(Message m, BdfList body) throws FormatException { + checkSize(body, 5); - checkSize(message, 2); + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); - // Return the metadata - return new BdfDictionary(); + byte[] previousMessageId = body.getRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + byte[] mac = body.getRaw(3); + checkLength(mac, MAC_BYTES); + + byte[] signature = body.getRaw(4); + checkLength(signature, 1, MAX_SIGNATURE_BYTES); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(AUTH, sessionId, m.getTimestamp(), false, false, + false); + 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, false, + false); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + MessageId dependency = new MessageId(previousMessageId); + return new BdfMessageContext(meta, + Collections.singletonList(dependency)); + } + } + } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java similarity index 97% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java index 390ea9b0f..d619e3d76 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoder.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.identity.Author; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java similarity index 83% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java index 7f44671e0..5bade9d86 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; @@ -20,19 +20,19 @@ import javax.annotation.Nullable; import javax.inject.Inject; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI; -import static org.briarproject.briar.introduction2.MessageType.ABORT; -import static org.briarproject.briar.introduction2.MessageType.ACCEPT; -import static org.briarproject.briar.introduction2.MessageType.ACTIVATE; -import static org.briarproject.briar.introduction2.MessageType.AUTH; -import static org.briarproject.briar.introduction2.MessageType.DECLINE; -import static org.briarproject.briar.introduction2.MessageType.REQUEST; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI; +import static org.briarproject.briar.introduction.MessageType.ABORT; +import static org.briarproject.briar.introduction.MessageType.ACCEPT; +import static org.briarproject.briar.introduction.MessageType.ACTIVATE; +import static org.briarproject.briar.introduction.MessageType.AUTH; +import static org.briarproject.briar.introduction.MessageType.DECLINE; +import static org.briarproject.briar.introduction.MessageType.REQUEST; @NotNullByDefault class MessageEncoderImpl implements MessageEncoder { diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageMetadata.java similarity index 96% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/MessageMetadata.java index 67ba5b031..9b3ea54b5 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageMetadata.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageMetadata.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.api.client.SessionId; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParser.java similarity index 95% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/MessageParser.java index 45526a091..58f9dbfab 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParser.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParser.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.data.BdfDictionary; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java similarity index 86% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java index 1c55be11d..263af7844 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; @@ -18,14 +18,14 @@ import java.util.Map; import javax.inject.Inject; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP; -import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI; -import static org.briarproject.briar.introduction2.MessageType.REQUEST; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI; +import static org.briarproject.briar.introduction.MessageType.REQUEST; @NotNullByDefault class MessageParserImpl implements MessageParser { diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageSender.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageSender.java deleted file mode 100644 index 7848aaf3d..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageSender.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.briarproject.briar.introduction; - -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.MetadataEncoder; -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.Group; -import org.briarproject.bramble.api.sync.GroupId; -import org.briarproject.bramble.api.system.Clock; -import org.briarproject.briar.api.client.MessageQueueManager; - -import javax.annotation.concurrent.Immutable; -import javax.inject.Inject; - -import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; - -@Immutable -@NotNullByDefault -class MessageSender { - - private final DatabaseComponent db; - private final ClientHelper clientHelper; - private final Clock clock; - private final MetadataEncoder metadataEncoder; - private final MessageQueueManager messageQueueManager; - - @Inject - MessageSender(DatabaseComponent db, ClientHelper clientHelper, Clock clock, - MetadataEncoder metadataEncoder, - MessageQueueManager messageQueueManager) { - - this.db = db; - this.clientHelper = clientHelper; - this.clock = clock; - this.metadataEncoder = metadataEncoder; - this.messageQueueManager = messageQueueManager; - } - - void sendMessage(Transaction txn, BdfDictionary message) - throws DbException, FormatException { - - BdfList bdfList = encodeMessage(message); - byte[] body = clientHelper.toByteArray(bdfList); - GroupId groupId = new GroupId(message.getRaw(GROUP_ID)); - Group group = db.getGroup(txn, groupId); - long timestamp = clock.currentTimeMillis(); - - message.put(MESSAGE_TIME, timestamp); - Metadata metadata = metadataEncoder.encode(message); - - messageQueueManager.sendMessage(txn, group, timestamp, body, metadata); - } - - private BdfList encodeMessage(BdfDictionary d) throws FormatException { - - BdfList body; - long type = d.getLong(TYPE); - if (type == TYPE_REQUEST) { - body = encodeRequest(d); - } else if (type == TYPE_RESPONSE) { - body = encodeResponse(d); - } else if (type == TYPE_ACK) { - body = encodeAck(d); - } else if (type == TYPE_ABORT) { - body = encodeAbort(d); - } else { - throw new FormatException(); - } - return body; - } - - private BdfList encodeRequest(BdfDictionary d) throws FormatException { - BdfList list = BdfList.of(TYPE_REQUEST, d.getRaw(SESSION_ID), - d.getString(NAME), d.getRaw(PUBLIC_KEY)); - - if (d.containsKey(MSG)) { - list.add(d.getString(MSG)); - } - return list; - } - - private BdfList encodeResponse(BdfDictionary d) throws FormatException { - BdfList list = BdfList.of(TYPE_RESPONSE, d.getRaw(SESSION_ID), - d.getBoolean(ACCEPT)); - - if (d.getBoolean(ACCEPT)) { - list.add(d.getLong(TIME)); - list.add(d.getRaw(E_PUBLIC_KEY)); - list.add(d.getDictionary(TRANSPORT)); - } - return list; - } - - private BdfList encodeAck(BdfDictionary d) throws FormatException { - return BdfList.of(TYPE_ACK, d.getRaw(SESSION_ID), d.getRaw(MAC), - d.getRaw(SIGNATURE)); - } - - private BdfList encodeAbort(BdfDictionary d) throws FormatException { - return BdfList.of(TYPE_ABORT, d.getRaw(SESSION_ID)); - } - -} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageType.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageType.java similarity index 92% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/MessageType.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/MessageType.java index bb34b7da3..67365399b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/MessageType.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageType.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/PeerSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction/PeerSession.java similarity index 91% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/PeerSession.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/PeerSession.java index e2f8b7c46..3c453d2fa 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/PeerSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/PeerSession.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/ProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/ProtocolEngine.java similarity index 96% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/ProtocolEngine.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/ProtocolEngine.java index 083296ca0..e3766c91a 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/ProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/ProtocolEngine.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.db.DbException; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/RequestMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction/RequestMessage.java similarity index 88% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/RequestMessage.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/RequestMessage.java index 470fb3966..743e87af8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/RequestMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/RequestMessage.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -class RequestMessage extends IntroductionMessage { +class RequestMessage extends AbstractIntroductionMessage { private final Author author; @Nullable diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/Session.java b/briar-core/src/main/java/org/briarproject/briar/introduction/Session.java similarity index 87% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/Session.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/Session.java index ca2a89c19..ddf04d044 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/Session.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/Session.java @@ -1,8 +1,8 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.api.introduction2.Role; +import org.briarproject.briar.api.introduction.Role; import javax.annotation.concurrent.Immutable; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoder.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoder.java similarity index 57% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoder.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoder.java index 4b45c39ca..70cfff1bb 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoder.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoder.java @@ -1,11 +1,16 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @NotNullByDefault interface SessionEncoder { + BdfDictionary getIntroduceeSessionsByIntroducerQuery(Author introducer); + + BdfDictionary getIntroducerSessionsQuery(); + BdfDictionary encodeIntroducerSession(IntroducerSession s); BdfDictionary encodeIntroduceeSession(IntroduceeSession s); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java similarity index 54% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoderImpl.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java index 3bce33d85..9cf17a2d0 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java @@ -1,11 +1,13 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; 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 org.briarproject.briar.introduction2.IntroducerSession.Introducee; +import org.briarproject.briar.introduction.IntroducerSession.Introducee; import java.util.Map; @@ -14,28 +16,30 @@ import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_AUTHOR; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_GROUP_ID; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCER; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_MASTER_KEY; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_SESSION_ID; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_STATE; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES; +import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; +import static org.briarproject.briar.api.introduction.Role.INTRODUCER; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_GROUP_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_STATE; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES; @Immutable @NotNullByDefault @@ -48,6 +52,23 @@ class SessionEncoderImpl implements SessionEncoder { 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(IntroducerSession s) { BdfDictionary d = encodeSession(s); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParser.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParser.java similarity index 86% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParser.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/SessionParser.java index dd3fba09c..c58cac3d9 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParser.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParser.java @@ -1,11 +1,11 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; 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.introduction2.Role; +import org.briarproject.briar.api.introduction.Role; @NotNullByDefault interface SessionParser { diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java similarity index 69% rename from briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParserImpl.java rename to briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java index dbb609fc1..f269b4973 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/SessionParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; @@ -12,8 +12,8 @@ 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.introduction2.Role; -import org.briarproject.briar.introduction2.IntroducerSession.Introducee; +import org.briarproject.briar.api.introduction.Role; +import org.briarproject.briar.introduction.IntroducerSession.Introducee; import java.util.HashMap; import java.util.Map; @@ -22,30 +22,30 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_AUTHOR; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_GROUP_ID; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCER; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_MASTER_KEY; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_SESSION_ID; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_STATE; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES; -import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; -import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_GROUP_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_STATE; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES; +import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; +import static org.briarproject.briar.api.introduction.Role.INTRODUCER; @Immutable @NotNullByDefault diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/State.java b/briar-core/src/main/java/org/briarproject/briar/introduction/State.java new file mode 100644 index 000000000..3063f9bd8 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/State.java @@ -0,0 +1,7 @@ +package org.briarproject.briar.introduction; + +interface State { + + int getValue(); + +} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionManagerImpl.java deleted file mode 100644 index 55a6e552a..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionManagerImpl.java +++ /dev/null @@ -1,459 +0,0 @@ -package org.briarproject.briar.introduction2; - -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.ContactHook; -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.identity.LocalAuthor; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -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.sync.MessageStatus; -import org.briarproject.briar.api.client.MessageTracker; -import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.api.introduction2.IntroductionManager; -import org.briarproject.briar.api.introduction2.IntroductionMessage; -import org.briarproject.briar.api.introduction2.IntroductionRequest; -import org.briarproject.briar.api.introduction2.IntroductionResponse; -import org.briarproject.briar.api.introduction2.Role; -import org.briarproject.briar.client.ConversationClientImpl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import javax.inject.Inject; - -import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; -import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; -import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; -import static org.briarproject.briar.introduction2.IntroductionConstants.GROUP_KEY_CONTACT_ID; -import static org.briarproject.briar.introduction2.MessageType.ABORT; -import static org.briarproject.briar.introduction2.MessageType.ACCEPT; -import static org.briarproject.briar.introduction2.MessageType.ACTIVATE; -import static org.briarproject.briar.introduction2.MessageType.AUTH; -import static org.briarproject.briar.introduction2.MessageType.DECLINE; -import static org.briarproject.briar.introduction2.MessageType.REQUEST; - -@Immutable -@NotNullByDefault -class IntroductionManagerImpl extends ConversationClientImpl - implements IntroductionManager, Client, ContactHook { - - private final ContactGroupFactory contactGroupFactory; - private final MessageParser messageParser; - private final SessionEncoder sessionEncoder; - private final SessionParser sessionParser; - private final IntroducerProtocolEngine introducerEngine; - private final IntroduceeProtocolEngine introduceeEngine; - private final IntroductionCrypto crypto; - private final IdentityManager identityManager; - - @Inject - IntroductionManagerImpl( - DatabaseComponent db, - ClientHelper clientHelper, - MetadataParser metadataParser, - MessageTracker messageTracker, - ContactGroupFactory contactGroupFactory, - MessageParser messageParser, - SessionEncoder sessionEncoder, - SessionParser sessionParser, - IntroducerProtocolEngine introducerEngine, - IntroduceeProtocolEngine introduceeEngine, - IntroductionCrypto crypto, - IdentityManager identityManager) { - super(db, clientHelper, metadataParser, messageTracker); - this.contactGroupFactory = contactGroupFactory; - this.messageParser = messageParser; - this.sessionEncoder = sessionEncoder; - this.sessionParser = sessionParser; - this.introducerEngine = introducerEngine; - this.introduceeEngine = introduceeEngine; - this.crypto = crypto; - this.identityManager = identityManager; - } - - @Override - public void createLocalState(Transaction txn) throws DbException { - // Create a local group to store protocol sessions - Group localGroup = getLocalGroup(); - 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.getContacts(txn)) addingContact(txn, c); - } - - @Override - // TODO adapt to use upcoming ClientVersioning client - public void addingContact(Transaction txn, Contact c) throws DbException { - // Create a group to share with the contact - Group g = getContactGroup(c); - // Return if we've already set things up for this contact - if (db.containsGroup(txn, g.getId())) return; - // Store the group and share it with the contact - db.addGroup(txn, g); - db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); - // 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); - } - } - - @Override - public void removingContact(Transaction txn, Contact c) throws DbException { - // Remove the contact group (all messages will be removed with it) - db.removeGroup(txn, getContactGroup(c)); - // TODO abort other sessions the contact is involved in - } - - @Override - public Group getContactGroup(Contact c) { - return contactGroupFactory - .createContactGroup(CLIENT_ID, CLIENT_VERSION, c); - } - - @Override - protected boolean incomingMessage(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 newIntroduceeSession = null; - if (sessionId == null) { - if (meta.getMessageType() != REQUEST) throw new AssertionError(); - newIntroduceeSession = createNewIntroduceeSession(txn, m, body); - sessionId = newIntroduceeSession.getSessionId(); - } - StoredSession ss = getSession(txn, sessionId); - // Handle the message - Session session; - MessageId storageId; - if (ss == null) { - if (meta.getMessageType() != REQUEST) throw new FormatException(); - if (newIntroduceeSession == null) throw new AssertionError(); - storageId = createStorageId(txn); - session = handleMessage(txn, m, body, meta.getMessageType(), - newIntroduceeSession, introduceeEngine); - } else { - storageId = ss.storageId; - Role role = sessionParser.getRole(ss.bdfSession); - if (role == INTRODUCER) { - session = handleMessage(txn, m, body, meta.getMessageType(), - sessionParser.parseIntroducerSession(ss.bdfSession), - introducerEngine); - } else if (role == INTRODUCEE) { - session = handleMessage(txn, m, body, meta.getMessageType(), - sessionParser.parseIntroduceeSession(m.getGroupId(), - ss.bdfSession), introduceeEngine); - } else throw new AssertionError(); - } - // Store the updated session - storeSession(txn, storageId, session); - return false; - } - - private IntroduceeSession createNewIntroduceeSession(Transaction txn, - Message m, BdfList body) throws DbException, FormatException { - ContactId introducerId = getContactId(txn, m.getGroupId()); - Author introducer = db.getContact(txn, introducerId).getAuthor(); - Author alice = identityManager.getLocalAuthor(txn); - Author bob = messageParser.parseRequestMessage(m, body).getAuthor(); - SessionId sessionId = crypto.getSessionId(introducer, alice, bob); - return IntroduceeSession - .getInitial(m.getGroupId(), sessionId, introducer, bob); - } - - private S handleMessage(Transaction txn, Message m, - BdfList body, MessageType type, S session, ProtocolEngine engine) - throws DbException, FormatException { - if (type == REQUEST) { - RequestMessage request = messageParser.parseRequestMessage(m, body); - return engine.onRequestMessage(txn, session, request); - } else if (type == ACCEPT) { - AcceptMessage accept = messageParser.parseAcceptMessage(m, body); - return engine.onAcceptMessage(txn, session, accept); - } else if (type == DECLINE) { - DeclineMessage decline = messageParser.parseDeclineMessage(m, body); - return engine.onDeclineMessage(txn, session, decline); - } else if (type == AUTH) { - AuthMessage auth = messageParser.parseAuthMessage(m, body); - return engine.onAuthMessage(txn, session, auth); - } else if (type == ACTIVATE) { - ActivateMessage activate = - messageParser.parseActivateMessage(m, body); - return engine.onActivateMessage(txn, session, activate); - } else if (type == ABORT) { - AbortMessage abort = messageParser.parseAbortMessage(m, body); - return engine.onAbortMessage(txn, session, abort); - } else { - throw new AssertionError(); - } - } - - @Nullable - private StoredSession getSession(Transaction txn, - @Nullable SessionId sessionId) throws DbException, FormatException { - if (sessionId == null) return null; - BdfDictionary query = sessionParser.getSessionQuery(sessionId); - Map results = clientHelper - .getMessageMetadataAsDictionary(txn, getLocalGroup().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()); - } - - 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()); - } - - private MessageId createStorageId(Transaction txn) throws DbException { - Message m = clientHelper - .createMessageForStoringMetadata(getLocalGroup().getId()); - db.addLocalMessage(txn, m, new Metadata(), false); - return m.getId(); - } - - private void storeSession(Transaction txn, MessageId storageId, - Session session) throws DbException, FormatException { - BdfDictionary d; - if (session.getRole() == INTRODUCER) { - d = sessionEncoder - .encodeIntroducerSession((IntroducerSession) session); - } else if (session.getRole() == INTRODUCEE) { - d = sessionEncoder - .encodeIntroduceeSession((IntroduceeSession) session); - } else { - throw new AssertionError(); - } - clientHelper.mergeMessageMetadata(txn, storageId, d); - } - - @Override - public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg, - 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, c1.getAuthor(), - c2.getAuthor()); - StoredSession ss = getSession(txn, sessionId); - // Create or parse the session - IntroducerSession session; - MessageId storageId; - if (ss == null) { - // This is the first request - create a new session - GroupId groupId1 = getContactGroup(c1).getId(); - GroupId groupId2 = getContactGroup(c2).getId(); - session = new IntroducerSession(sessionId, groupId1, - c1.getAuthor(), groupId2, c2.getAuthor()); - storageId = createStorageId(txn); - } else { - // An earlier request exists, so we already have a session - session = sessionParser.parseIntroducerSession(ss.bdfSession); - storageId = ss.storageId; - } - // Handle the request action - session = introducerEngine - .onRequestAction(txn, session, msg, timestamp); - // Store the updated session - storeSession(txn, storageId, session); - db.commitTransaction(txn); - } catch (FormatException e) { - throw new DbException(e); - } finally { - db.endTransaction(txn); - } - } - - @Override - public void acceptIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException { - respondToRequest(contactId, sessionId, timestamp, true); - } - - @Override - public void declineIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException { - respondToRequest(contactId, sessionId, timestamp, false); - } - - private void respondToRequest(ContactId contactId, SessionId sessionId, - long timestamp, boolean accept) throws DbException { - Transaction txn = db.startTransaction(false); - try { - // Look up the session - StoredSession ss = getSession(txn, sessionId); - if (ss == null) throw new IllegalArgumentException(); - // Parse the session - Contact contact = db.getContact(txn, contactId); - GroupId contactGroupId = getContactGroup(contact).getId(); - IntroduceeSession session = sessionParser - .parseIntroduceeSession(contactGroupId, ss.bdfSession); - // Handle the join or leave action - if (accept) { - session = introduceeEngine - .onAcceptAction(txn, session, timestamp); - } else { - session = introduceeEngine - .onDeclineAction(txn, session, timestamp); - } - // Store the updated session - storeSession(txn, ss.storageId, session); - db.commitTransaction(txn); - } catch (FormatException e) { - throw new DbException(e); - } finally { - db.endTransaction(txn); - } - } - - @Override - public Collection getIntroductionMessages(ContactId c) - throws DbException { - List messages; - Transaction txn = db.startTransaction(true); - try { - Contact contact = db.getContact(txn, c); - GroupId contactGroupId = getContactGroup(contact).getId(); - BdfDictionary query = messageParser.getMessagesVisibleInUiQuery(); - Map results = clientHelper - .getMessageMetadataAsDictionary(txn, contactGroupId, query); - messages = new ArrayList<>(results.size()); - for (Map.Entry e : results.entrySet()) { - MessageId m = e.getKey(); - MessageMetadata meta = - messageParser.parseMetadata(e.getValue()); - MessageStatus status = db.getMessageStatus(txn, c, m); - StoredSession ss = getSession(txn, meta.getSessionId()); - if (ss == null) throw new AssertionError(); - MessageType type = meta.getMessageType(); - if (type == REQUEST) { - messages.add( - parseInvitationRequest(txn, contactGroupId, m, - meta, status, ss.bdfSession)); - } else if (type == ACCEPT) { - messages.add( - parseInvitationResponse(txn, contactGroupId, m, - meta, status, ss.bdfSession, true)); - } else if (type == DECLINE) { - messages.add( - parseInvitationResponse(txn, contactGroupId, m, - meta, status, ss.bdfSession, false)); - } - } - db.commitTransaction(txn); - } catch (FormatException e) { - throw new DbException(e); - } finally { - db.endTransaction(txn); - } - return messages; - } - - private IntroductionRequest parseInvitationRequest(Transaction txn, - GroupId contactGroupId, MessageId m, MessageMetadata meta, - MessageStatus status, BdfDictionary bdfSession) - throws DbException, FormatException { - Role role = sessionParser.getRole(bdfSession); - SessionId sessionId; - Author author; - if (role == INTRODUCER) { - IntroducerSession session = - sessionParser.parseIntroducerSession(bdfSession); - sessionId = session.getSessionId(); - LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); - if (localAuthor.equals(session.getIntroducee1().author)) { - author = session.getIntroducee2().author; - } else { - author = session.getIntroducee1().author; - } - } else if (role == INTRODUCEE) { - IntroduceeSession session = sessionParser - .parseIntroduceeSession(contactGroupId, bdfSession); - sessionId = session.getSessionId(); - author = session.getRemoteAuthor(); - } else throw new AssertionError(); - String message = ""; // TODO - boolean contactExists = false; // TODO - - return new IntroductionRequest(sessionId, m, contactGroupId, - role, meta.getTimestamp(), meta.isLocal(), - status.isSent(), status.isSeen(), meta.isRead(), - author.getName(), false, message, !meta.isAvailableToAnswer(), - contactExists); - } - - private IntroductionResponse parseInvitationResponse(Transaction txn, - GroupId contactGroupId, MessageId m, MessageMetadata meta, - MessageStatus status, BdfDictionary bdfSession, boolean accept) - throws FormatException, DbException { - Role role = sessionParser.getRole(bdfSession); - SessionId sessionId; - Author author; - if (role == INTRODUCER) { - IntroducerSession session = - sessionParser.parseIntroducerSession(bdfSession); - sessionId = session.getSessionId(); - LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); - if (localAuthor.equals(session.getIntroducee1().author)) { - author = session.getIntroducee2().author; - } else { - author = session.getIntroducee1().author; - } - } else if (role == INTRODUCEE) { - IntroduceeSession session = sessionParser - .parseIntroduceeSession(contactGroupId, bdfSession); - sessionId = session.getSessionId(); - author = session.getRemoteAuthor(); - } else throw new AssertionError(); - return new IntroductionResponse(sessionId, m, contactGroupId, - role, meta.getTimestamp(), meta.isLocal(), status.isSent(), - status.isSeen(), meta.isRead(), author.getName(), accept); - } - - private Group getLocalGroup() { - return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); - } - - 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/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionModule.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionModule.java deleted file mode 100644 index 1a64538ea..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionModule.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.briarproject.briar.introduction2; - -import org.briarproject.bramble.api.client.ClientHelper; -import org.briarproject.bramble.api.data.MetadataEncoder; -import org.briarproject.bramble.api.sync.ValidationManager; -import org.briarproject.bramble.api.system.Clock; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import dagger.Module; -import dagger.Provides; - -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; - -@Module -public class IntroductionModule { - - public static class EagerSingletons { - @Inject - IntroductionValidator introductionValidator; - } - - @Provides - @Singleton - IntroductionValidator provideValidator(ValidationManager validationManager, - MessageEncoder messageEncoder, MetadataEncoder metadataEncoder, - ClientHelper clientHelper, Clock clock) { - - IntroductionValidator introductionValidator = - new IntroductionValidator(messageEncoder, clientHelper, - metadataEncoder, clock); - validationManager.registerMessageValidator(CLIENT_ID, - introductionValidator); - - return introductionValidator; - } - -} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java deleted file mode 100644 index 1bdf07ff6..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionValidator.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.briarproject.briar.introduction2; - -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.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; -import static org.briarproject.bramble.util.ValidationUtils.checkLength; -import static org.briarproject.bramble.util.ValidationUtils.checkSize; -import static org.briarproject.briar.api.introduction2.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; -import static org.briarproject.briar.introduction2.MessageType.ACCEPT; -import static org.briarproject.briar.introduction2.MessageType.AUTH; - - -@Immutable -@NotNullByDefault -class IntroductionValidator extends BdfMessageValidator { - - private final MessageEncoder messageEncoder; - - IntroductionValidator(MessageEncoder 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 REQUEST: - return validateRequestMessage(m, body); - case ACCEPT: - return validateAcceptMessage(m, body); - case AUTH: - return validateAuthMessage(m, body); - case DECLINE: - case ACTIVATE: - 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); - - String msg = body.getOptionalString(3); - checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH); - - BdfDictionary meta = messageEncoder - .encodeRequestMetadata(m.getTimestamp(), false, false, - false, false); - 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.getRaw(2); - checkLength(previousMessageId, UniqueId.LENGTH); - - byte[] ephemeralPublicKey = body.getRaw(3); - checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH); - - body.getLong(4); - - BdfDictionary transportProperties = body.getDictionary(5); - if (transportProperties.size() < 1) throw new FormatException(); - for (String tId : transportProperties.keySet()) { - checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH); - BdfDictionary tProps = transportProperties.getDictionary(tId); - clientHelper.parseAndValidateTransportProperties(tProps); - } - - SessionId sessionId = new SessionId(sessionIdBytes); - BdfDictionary meta = messageEncoder - .encodeMetadata(ACCEPT, sessionId, m.getTimestamp(), false, - false, false); - MessageId dependency = new MessageId(previousMessageId); - return new BdfMessageContext(meta, - Collections.singletonList(dependency)); - } - - private BdfMessageContext validateAuthMessage(Message m, BdfList body) - throws FormatException { - checkSize(body, 5); - - byte[] sessionIdBytes = body.getRaw(1); - checkLength(sessionIdBytes, UniqueId.LENGTH); - - byte[] previousMessageId = body.getRaw(2); - checkLength(previousMessageId, UniqueId.LENGTH); - - byte[] mac = body.getRaw(3); - checkLength(mac, MAC_BYTES); - - byte[] signature = body.getRaw(4); - checkLength(signature, 1, MAX_SIGNATURE_BYTES); - - SessionId sessionId = new SessionId(sessionIdBytes); - BdfDictionary meta = messageEncoder - .encodeMetadata(AUTH, sessionId, m.getTimestamp(), false, false, - false); - 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.getRaw(2); - checkLength(previousMessageId, UniqueId.LENGTH); - - SessionId sessionId = new SessionId(sessionIdBytes); - BdfDictionary meta = messageEncoder - .encodeMetadata(type, sessionId, m.getTimestamp(), false, false, - false); - MessageId dependency = new MessageId(previousMessageId); - return new BdfMessageContext(meta, - Collections.singletonList(dependency)); - } - -} diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/State.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/State.java deleted file mode 100644 index 1e1d46e0a..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/introduction2/State.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.briarproject.briar.introduction2; - -interface State { - - int getValue(); - -} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java deleted file mode 100644 index 5f8391d9b..000000000 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java +++ /dev/null @@ -1,424 +0,0 @@ -package org.briarproject.briar.introduction; - -import org.briarproject.bramble.api.Bytes; -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.client.ClientHelper; -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.crypto.CryptoComponent; -import org.briarproject.bramble.api.crypto.SecretKey; -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.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.AuthorFactory; -import org.briarproject.bramble.api.identity.AuthorId; -import org.briarproject.bramble.api.identity.IdentityManager; -import org.briarproject.bramble.api.properties.TransportPropertyManager; -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.briar.api.client.SessionId; -import org.briarproject.briar.api.introduction.IntroduceeProtocolState; -import org.briarproject.briar.test.BriarTestCase; -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.jmock.lib.legacy.ClassImposteriser; -import org.junit.Test; - -import java.security.GeneralSecurityException; -import java.security.SecureRandom; - -import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; -import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; -import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; -import static org.briarproject.bramble.test.TestUtils.getAuthor; -import static org.briarproject.bramble.test.TestUtils.getGroup; -import static org.briarproject.bramble.test.TestUtils.getRandomBytes; -import static org.briarproject.bramble.test.TestUtils.getRandomId; -import static org.briarproject.bramble.test.TestUtils.getSecretKey; -import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ADDED_CONTACT_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS; -import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.INTRODUCER; -import static org.briarproject.briar.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; -import static org.hamcrest.Matchers.array; -import static org.hamcrest.Matchers.samePropertyValuesAs; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -public class IntroduceeManagerTest extends BriarTestCase { - - private final Mockery context; - private final IntroduceeManager introduceeManager; - private final DatabaseComponent db; - private final CryptoComponent cryptoComponent; - private final ClientHelper clientHelper; - private final IntroductionGroupFactory introductionGroupFactory; - private final AuthorFactory authorFactory; - private final ContactManager contactManager; - private final Clock clock; - private final Contact introducer; - private final Contact introducee1; - private final Contact introducee2; - private final Group localGroup1; - private final Group introductionGroup1; - private final Transaction txn; - private final long time = 42L; - private final Message localStateMessage; - private final SessionId sessionId; - private final Message message1; - - public IntroduceeManagerTest() { - context = new Mockery(); - context.setImposteriser(ClassImposteriser.INSTANCE); - MessageSender messageSender = context.mock(MessageSender.class); - db = context.mock(DatabaseComponent.class); - cryptoComponent = context.mock(CryptoComponent.class); - clientHelper = context.mock(ClientHelper.class); - clock = context.mock(Clock.class); - introductionGroupFactory = - context.mock(IntroductionGroupFactory.class); - TransportPropertyManager transportPropertyManager = - context.mock(TransportPropertyManager.class); - authorFactory = context.mock(AuthorFactory.class); - contactManager = context.mock(ContactManager.class); - IdentityManager identityManager = context.mock(IdentityManager.class); - - introduceeManager = new IntroduceeManager(messageSender, db, - clientHelper, clock, cryptoComponent, transportPropertyManager, - authorFactory, contactManager, identityManager, - introductionGroupFactory); - - Author author0 = getAuthor(); - AuthorId localAuthorId = new AuthorId(getRandomId()); - ContactId contactId0 = new ContactId(234); - introducer = - new Contact(contactId0, author0, localAuthorId, true, true); - - Author author1 = getAuthor(); - AuthorId localAuthorId1 = new AuthorId(getRandomId()); - ContactId contactId1 = new ContactId(234); - introducee1 = - new Contact(contactId1, author1, localAuthorId1, true, true); - - Author author2 = getAuthor(); - ContactId contactId2 = new ContactId(235); - introducee2 = - new Contact(contactId2, author2, localAuthorId, true, true); - - localGroup1 = getGroup(CLIENT_ID); - introductionGroup1 = getGroup(CLIENT_ID); - - sessionId = new SessionId(getRandomId()); - localStateMessage = new Message( - new MessageId(getRandomId()), - localGroup1.getId(), - time, - getRandomBytes(MESSAGE_HEADER_LENGTH + 1) - ); - message1 = new Message( - new MessageId(getRandomId()), - introductionGroup1.getId(), - time, - getRandomBytes(MESSAGE_HEADER_LENGTH + 1) - ); - - txn = new Transaction(null, false); - } - - @Test - public void testIncomingRequestMessage() - throws DbException, FormatException { - - BdfDictionary msg = new BdfDictionary(); - msg.put(TYPE, TYPE_REQUEST); - msg.put(GROUP_ID, introductionGroup1.getId()); - msg.put(SESSION_ID, sessionId); - msg.put(MESSAGE_ID, message1.getId()); - msg.put(MESSAGE_TIME, time); - msg.put(NAME, introducee2.getAuthor().getName()); - msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey()); - - BdfDictionary state = - initializeSessionState(txn, introductionGroup1.getId(), msg); - - context.checking(new Expectations() {{ - oneOf(clientHelper).mergeMessageMetadata(txn, - localStateMessage.getId(), state); - }}); - - introduceeManager.incomingMessage(txn, state, msg); - - context.assertIsSatisfied(); - assertFalse(txn.isCommitted()); - } - - @Test - public void testIncomingResponseMessage() - throws DbException, FormatException { - - BdfDictionary msg = new BdfDictionary(); - msg.put(TYPE, TYPE_RESPONSE); - msg.put(GROUP_ID, introductionGroup1.getId()); - msg.put(SESSION_ID, sessionId); - msg.put(MESSAGE_ID, message1.getId()); - msg.put(MESSAGE_TIME, time); - msg.put(NAME, introducee2.getAuthor().getName()); - msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey()); - - BdfDictionary state = - initializeSessionState(txn, introductionGroup1.getId(), msg); - state.put(STATE, IntroduceeProtocolState.AWAIT_RESPONSES.ordinal()); - - // turn request message into a response - msg.put(ACCEPT, true); - msg.put(TIME, time); - msg.put(E_PUBLIC_KEY, getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES)); - msg.put(TRANSPORT, new BdfDictionary()); - - context.checking(new Expectations() {{ - oneOf(clientHelper).mergeMessageMetadata(txn, - localStateMessage.getId(), state); - }}); - - introduceeManager.incomingMessage(txn, state, msg); - - context.assertIsSatisfied(); - assertFalse(txn.isCommitted()); - } - - @Test - public void testDetectReplacedEphemeralPublicKey() - throws DbException, FormatException, GeneralSecurityException { - - // TODO MR !237 should use its new default initialization method here - BdfDictionary msg = new BdfDictionary(); - msg.put(TYPE, TYPE_RESPONSE); - msg.put(GROUP_ID, introductionGroup1.getId()); - msg.put(SESSION_ID, sessionId); - msg.put(MESSAGE_ID, message1.getId()); - msg.put(MESSAGE_TIME, time); - msg.put(NAME, introducee2.getAuthor().getName()); - msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey()); - BdfDictionary state = - initializeSessionState(txn, introductionGroup1.getId(), msg); - - // prepare state for incoming ACK - state.put(STATE, IntroduceeProtocolState.AWAIT_ACK.ordinal()); - state.put(ADDED_CONTACT_ID, 2); - byte[] nonce = getRandomBytes(42); - state.put(NONCE, nonce); - state.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey()); - - // create incoming ACK message - byte[] mac = getRandomBytes(MAC_LENGTH); - byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH); - BdfDictionary ack = BdfDictionary.of( - new BdfEntry(TYPE, TYPE_ACK), - new BdfEntry(SESSION_ID, sessionId), - new BdfEntry(GROUP_ID, introductionGroup1.getId()), - new BdfEntry(MAC, mac), - new BdfEntry(SIGNATURE, sig) - ); - - context.checking(new Expectations() {{ - oneOf(cryptoComponent).verifySignature(sig, SIGNING_LABEL, nonce, - introducee2.getAuthor().getPublicKey()); - will(returnValue(false)); - }}); - - try { - introduceeManager.incomingMessage(txn, state, ack); - fail(); - } catch (DbException e) { - // expected - assertTrue(e.getCause() instanceof GeneralSecurityException); - } - context.assertIsSatisfied(); - assertFalse(txn.isCommitted()); - } - - @Test - public void testSignatureVerification() - throws FormatException, DbException, GeneralSecurityException { - - byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey(); - byte[] nonce = getRandomBytes(MAC_LENGTH); - byte[] sig = getRandomBytes(MAC_LENGTH); - - BdfDictionary state = new BdfDictionary(); - state.put(PUBLIC_KEY, publicKeyBytes); - state.put(NONCE, nonce); - state.put(SIGNATURE, sig); - - context.checking(new Expectations() {{ - oneOf(cryptoComponent).verifySignature(sig, SIGNING_LABEL, nonce, - publicKeyBytes); - will(returnValue(true)); - }}); - introduceeManager.verifySignature(state); - context.assertIsSatisfied(); - } - - @Test - public void testMacVerification() - throws FormatException, DbException, GeneralSecurityException { - - byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey(); - BdfDictionary tp = BdfDictionary.of(new BdfEntry("fake", "fake")); - byte[] ePublicKeyBytes = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES); - byte[] mac = getRandomBytes(MAC_LENGTH); - SecretKey macKey = getSecretKey(); - - // move state to where it would be after an ACK arrived - BdfDictionary state = new BdfDictionary(); - state.put(PUBLIC_KEY, publicKeyBytes); - state.put(TRANSPORT, tp); - state.put(TIME, time); - state.put(E_PUBLIC_KEY, ePublicKeyBytes); - state.put(MAC, mac); - state.put(MAC_KEY, macKey.getBytes()); - - byte[] signBytes = getRandomBytes(42); - context.checking(new Expectations() {{ - oneOf(clientHelper).toByteArray( - BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time)); - will(returnValue(signBytes)); - //noinspection unchecked - oneOf(cryptoComponent).mac(with(MAC_LABEL), - with(samePropertyValuesAs(macKey)), - with(array(equal(signBytes)))); - will(returnValue(mac)); - }}); - introduceeManager.verifyMac(state); - context.assertIsSatisfied(); - - // now produce wrong MAC - context.checking(new Expectations() {{ - oneOf(clientHelper).toByteArray( - BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time)); - will(returnValue(signBytes)); - //noinspection unchecked - oneOf(cryptoComponent).mac(with(MAC_LABEL), - with(samePropertyValuesAs(macKey)), - with(array(equal(signBytes)))); - will(returnValue(getRandomBytes(MAC_LENGTH))); - }}); - try { - introduceeManager.verifyMac(state); - fail(); - } catch (GeneralSecurityException e) { - // expected - } - context.assertIsSatisfied(); - } - - private BdfDictionary initializeSessionState(Transaction txn, - GroupId groupId, BdfDictionary msg) - throws DbException, FormatException { - - SecureRandom secureRandom = context.mock(SecureRandom.class); - Bytes salt = new Bytes(new byte[64]); - BdfDictionary groupMetadata = BdfDictionary.of( - new BdfEntry(CONTACT, introducee1.getId().getInt()) - ); - boolean contactExists = false; - BdfDictionary state = new BdfDictionary(); - state.put(STORAGE_ID, localStateMessage.getId()); - state.put(STATE, AWAIT_REQUEST.getValue()); - state.put(ROLE, ROLE_INTRODUCEE); - state.put(GROUP_ID, groupId); - state.put(INTRODUCER, introducer.getAuthor().getName()); - state.put(CONTACT_ID_1, introducer.getId().getInt()); - state.put(LOCAL_AUTHOR_ID, introducer.getLocalAuthorId().getBytes()); - state.put(NOT_OUR_RESPONSE, localStateMessage.getId()); - state.put(ANSWERED, false); - state.put(EXISTS, contactExists); - state.put(REMOTE_AUTHOR_ID, introducee2.getAuthor().getId()); - state.put(REMOTE_AUTHOR_IS_US, false); - - context.checking(new Expectations() {{ - oneOf(clock).currentTimeMillis(); - will(returnValue(time)); - oneOf(cryptoComponent).getSecureRandom(); - will(returnValue(secureRandom)); - oneOf(secureRandom).nextBytes(salt.getBytes()); - oneOf(introductionGroupFactory).createLocalGroup(); - will(returnValue(localGroup1)); - oneOf(clientHelper) - .createMessage(localGroup1.getId(), time, BdfList.of(salt)); - will(returnValue(localStateMessage)); - - // who is making the introduction? who is the introducer? - oneOf(clientHelper).getGroupMetadataAsDictionary(txn, - groupId); - will(returnValue(groupMetadata)); - oneOf(db).getContact(txn, introducer.getId()); - will(returnValue(introducer)); - - // create remote author to check if contact exists - oneOf(authorFactory).createAuthor(introducee2.getAuthor().getName(), - introducee2.getAuthor().getPublicKey()); - will(returnValue(introducee2.getAuthor())); - oneOf(contactManager) - .contactExists(txn, introducee2.getAuthor().getId(), - introducer.getLocalAuthorId()); - will(returnValue(contactExists)); - - // checks if remote author is one of our identities - oneOf(db).containsLocalAuthor(txn, introducee2.getAuthor().getId()); - will(returnValue(false)); - - // store session state - oneOf(clientHelper) - .addLocalMessage(txn, localStateMessage, state, false); - }}); - - BdfDictionary result = introduceeManager.initialize(txn, groupId, msg); - - context.assertIsSatisfied(); - assertFalse(txn.isCommitted()); - return result; - } - -} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroducerManagerTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroducerManagerTest.java deleted file mode 100644 index 558b26ec9..000000000 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroducerManagerTest.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.briarproject.briar.introduction; - -import org.briarproject.bramble.api.Bytes; -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.client.ClientHelper; -import org.briarproject.bramble.api.contact.Contact; -import org.briarproject.bramble.api.contact.ContactId; -import org.briarproject.bramble.api.crypto.CryptoComponent; -import org.briarproject.bramble.api.data.BdfDictionary; -import org.briarproject.bramble.api.data.BdfList; -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.AuthorId; -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.test.BriarTestCase; -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.jmock.lib.legacy.ClassImposteriser; -import org.junit.Test; - -import java.security.SecureRandom; - -import static org.briarproject.bramble.test.TestUtils.getAuthor; -import static org.briarproject.bramble.test.TestUtils.getGroup; -import static org.briarproject.bramble.test.TestUtils.getRandomBytes; -import static org.briarproject.bramble.test.TestUtils.getRandomId; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES; -import static org.briarproject.briar.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS; -import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; -import static org.junit.Assert.assertFalse; - -public class IntroducerManagerTest extends BriarTestCase { - - private final Mockery context; - private final IntroducerManager introducerManager; - private final CryptoComponent cryptoComponent; - private final ClientHelper clientHelper; - private final IntroductionGroupFactory introductionGroupFactory; - private final MessageSender messageSender; - private final Clock clock; - private final Contact introducee1; - private final Contact introducee2; - private final Group localGroup0; - private final Group introductionGroup1; - private final Group introductionGroup2; - - public IntroducerManagerTest() { - context = new Mockery(); - context.setImposteriser(ClassImposteriser.INSTANCE); - messageSender = context.mock(MessageSender.class); - cryptoComponent = context.mock(CryptoComponent.class); - clientHelper = context.mock(ClientHelper.class); - clock = context.mock(Clock.class); - introductionGroupFactory = - context.mock(IntroductionGroupFactory.class); - - introducerManager = - new IntroducerManager(messageSender, clientHelper, clock, - cryptoComponent, introductionGroupFactory); - - Author author1 = getAuthor(); - AuthorId localAuthorId1 = new AuthorId(getRandomId()); - ContactId contactId1 = new ContactId(234); - introducee1 = - new Contact(contactId1, author1, localAuthorId1, true, true); - - Author author2 = getAuthor(); - AuthorId localAuthorId2 = new AuthorId(getRandomId()); - ContactId contactId2 = new ContactId(235); - introducee2 = - new Contact(contactId2, author2, localAuthorId2, true, true); - - localGroup0 = getGroup(CLIENT_ID); - introductionGroup1 = getGroup(CLIENT_ID); - introductionGroup2 = getGroup(CLIENT_ID); - - context.assertIsSatisfied(); - } - - @Test - public void testMakeIntroduction() throws DbException, FormatException { - Transaction txn = new Transaction(null, false); - long time = 42L; - context.setImposteriser(ClassImposteriser.INSTANCE); - SecureRandom secureRandom = context.mock(SecureRandom.class); - Bytes salt = new Bytes(new byte[64]); - Message msg = new Message(new MessageId(getRandomId()), - localGroup0.getId(), time, getRandomBytes(64)); - BdfDictionary state = new BdfDictionary(); - state.put(SESSION_ID, msg.getId()); - state.put(STORAGE_ID, msg.getId()); - state.put(STATE, PREPARE_REQUESTS.getValue()); - state.put(ROLE, ROLE_INTRODUCER); - state.put(GROUP_ID_1, introductionGroup1.getId()); - state.put(GROUP_ID_2, introductionGroup2.getId()); - state.put(CONTACT_1, introducee1.getAuthor().getName()); - state.put(CONTACT_2, introducee2.getAuthor().getName()); - state.put(CONTACT_ID_1, introducee1.getId().getInt()); - state.put(CONTACT_ID_2, introducee2.getId().getInt()); - state.put(AUTHOR_ID_1, introducee1.getAuthor().getId()); - state.put(AUTHOR_ID_2, introducee2.getAuthor().getId()); - BdfDictionary state2 = (BdfDictionary) state.clone(); - state2.put(STATE, AWAIT_RESPONSES.getValue()); - - BdfDictionary msg1 = new BdfDictionary(); - msg1.put(TYPE, TYPE_REQUEST); - msg1.put(SESSION_ID, state.getRaw(SESSION_ID)); - msg1.put(GROUP_ID, state.getRaw(GROUP_ID_1)); - msg1.put(NAME, state.getString(CONTACT_2)); - msg1.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey()); - BdfDictionary msg1send = (BdfDictionary) msg1.clone(); - msg1send.put(MESSAGE_TIME, time); - - BdfDictionary msg2 = new BdfDictionary(); - msg2.put(TYPE, TYPE_REQUEST); - msg2.put(SESSION_ID, state.getRaw(SESSION_ID)); - msg2.put(GROUP_ID, state.getRaw(GROUP_ID_2)); - msg2.put(NAME, state.getString(CONTACT_1)); - msg2.put(PUBLIC_KEY, introducee1.getAuthor().getPublicKey()); - BdfDictionary msg2send = (BdfDictionary) msg2.clone(); - msg2send.put(MESSAGE_TIME, time); - - context.checking(new Expectations() {{ - // initialize and store session state - oneOf(clock).currentTimeMillis(); - will(returnValue(time)); - oneOf(cryptoComponent).getSecureRandom(); - will(returnValue(secureRandom)); - oneOf(secureRandom).nextBytes(salt.getBytes()); - oneOf(introductionGroupFactory).createLocalGroup(); - will(returnValue(localGroup0)); - oneOf(clientHelper).createMessage(localGroup0.getId(), time, - BdfList.of(salt)); - will(returnValue(msg)); - oneOf(introductionGroupFactory) - .createIntroductionGroup(introducee1); - will(returnValue(introductionGroup1)); - oneOf(introductionGroupFactory) - .createIntroductionGroup(introducee2); - will(returnValue(introductionGroup2)); - oneOf(clientHelper).addLocalMessage(txn, msg, state, false); - - // send message - oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), state2); - oneOf(messageSender).sendMessage(txn, msg1send); - oneOf(messageSender).sendMessage(txn, msg2send); - }}); - - introducerManager - .makeIntroduction(txn, introducee1, introducee2, null, time); - - context.assertIsSatisfied(); - assertFalse(txn.isCommitted()); - } - -} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java similarity index 99% rename from briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java rename to briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java index 57cadce0e..b8c636400 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.crypto.CryptoComponent; diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java similarity index 91% rename from briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java rename to briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java index 139c4ca40..a28e8321e 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.client.ClientHelper; @@ -11,7 +11,7 @@ import org.junit.Test; import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; -import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID; +import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID; import static org.junit.Assert.assertEquals; public class IntroductionCryptoTest extends BrambleMockTestCase { diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 9b485e5c4..6d13b80f1 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -6,23 +6,21 @@ import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; -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.data.BdfEntry; import org.briarproject.bramble.api.data.BdfList; 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.event.Event; import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportPropertyManager; 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.test.TestDatabaseModule; import org.briarproject.briar.api.client.SessionId; @@ -38,56 +36,35 @@ import org.junit.Before; import org.junit.Test; import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.TimeoutException; import java.util.logging.Logger; -import javax.inject.Inject; - import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getTransportId; -import static org.briarproject.bramble.util.StringUtils.getRandomString; -import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_MAC_KEY_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_NONCE_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SHARED_SECRET_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; +import static org.briarproject.bramble.test.TestUtils.getTransportProperties; +import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; +import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION; +import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID; +import static org.briarproject.briar.introduction.MessageType.ACCEPT; import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; public class IntroductionIntegrationTest extends BriarIntegrationTest { - @Inject - IntroductionGroupFactory introductionGroupFactory; - // objects accessed from background threads need to be volatile private volatile IntroductionManager introductionManager0; private volatile IntroductionManager introductionManager1; @@ -102,7 +79,7 @@ public class IntroductionIntegrationTest Logger.getLogger(IntroductionIntegrationTest.class.getName()); interface StateVisitor { - boolean visit(BdfDictionary response); + AcceptMessage visit(AcceptMessage response); } @Before @@ -151,50 +128,50 @@ public class IntroductionIntegrationTest .makeIntroduction(introducee1, introducee2, "Hi!", time); // check that messages are tracked properly - Group g1 = introductionGroupFactory - .createIntroductionGroup(introducee1); - Group g2 = introductionGroupFactory - .createIntroductionGroup(introducee2); - assertGroupCount(messageTracker0, g1.getId(), 1, 0, time); - assertGroupCount(messageTracker0, g2.getId(), 1, 0, time); + Group g1 = introductionManager0.getContactGroup(introducee1); + Group g2 = introductionManager0.getContactGroup(introducee2); + assertGroupCount(messageTracker0, g1.getId(), 1, 0); + assertGroupCount(messageTracker0, g2.getId(), 1, 0); - // sync first request message + // sync first REQUEST message sync0To1(1, true); eventWaiter.await(TIMEOUT, 1); assertTrue(listener1.requestReceived); assertGroupCount(messageTracker1, g1.getId(), 2, 1); - // sync second request message + // sync second REQUEST message sync0To2(1, true); eventWaiter.await(TIMEOUT, 1); assertTrue(listener2.requestReceived); assertGroupCount(messageTracker2, g2.getId(), 2, 1); - // sync first response + // sync first ACCEPT message sync1To0(1, true); eventWaiter.await(TIMEOUT, 1); assertTrue(listener0.response1Received); assertGroupCount(messageTracker0, g1.getId(), 2, 1); - // sync second response + // sync second ACCEPT message sync2To0(1, true); eventWaiter.await(TIMEOUT, 1); assertTrue(listener0.response2Received); assertGroupCount(messageTracker0, g2.getId(), 2, 1); - // sync forwarded responses to introducees + // sync forwarded ACCEPT messages to introducees sync0To1(1, true); sync0To2(1, true); - assertGroupCount(messageTracker1, g1.getId(), 2, 1); - assertGroupCount(messageTracker2, g2.getId(), 2, 1); - // sync first ACK and its forward + // sync first AUTH and its forward sync1To0(1, true); sync0To2(1, true); - // sync second ACK and its forward - sync2To0(1, true); - sync0To1(1, true); + // sync second AUTH and its forward as well as the following ACTIVATE + sync2To0(2, true); + sync0To1(2, true); + + // sync first ACTIVATE and its forward + sync1To0(1, true); + sync0To2(1, true); // wait for introduction to succeed eventWaiter.await(TIMEOUT, 2); @@ -269,10 +246,8 @@ public class IntroductionIntegrationTest assertFalse(contactManager2 .contactExists(author1.getId(), author2.getId())); - Group g1 = introductionGroupFactory - .createIntroductionGroup(introducee1); - Group g2 = introductionGroupFactory - .createIntroductionGroup(introducee2); + Group g1 = introductionManager0.getContactGroup(introducee1); + Group g2 = introductionManager0.getContactGroup(introducee2); assertEquals(2, introductionManager0.getIntroductionMessages(contactId1From0) .size()); @@ -290,6 +265,10 @@ public class IntroductionIntegrationTest introductionManager2.getIntroductionMessages(contactId0From2) .size()); assertGroupCount(messageTracker2, g2.getId(), 3, 2); + + assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertFalse(listener2.aborted); } @Test @@ -342,6 +321,9 @@ public class IntroductionIntegrationTest assertEquals(2, introductionManager2.getIntroductionMessages(contactId0From2) .size()); + assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertFalse(listener2.aborted); } @Test @@ -393,6 +375,9 @@ public class IntroductionIntegrationTest // since introducee2 was already in FINISHED state when // introducee1's response arrived, she ignores and deletes it assertDefaultUiMessages(); + assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertFalse(listener2.aborted); } @Test @@ -432,6 +417,8 @@ public class IntroductionIntegrationTest eventWaiter.await(TIMEOUT, 1); assertTrue(listener0.response2Received); assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertFalse(listener2.aborted); } @Test @@ -452,61 +439,11 @@ public class IntroductionIntegrationTest // make really sure we don't have that request assertTrue(introductionManager1.getIntroductionMessages(contactId0From1) .isEmpty()); - } - @Test - public void testSessionIdReuse() throws Exception { - addListeners(true, true); - - // make introduction - long time = clock.currentTimeMillis(); - introductionManager0 - .makeIntroduction(contact1From0, contact2From0, "Hi!", time); - - // sync first request message - sync0To1(1, true); - eventWaiter.await(TIMEOUT, 1); - assertTrue(listener1.requestReceived); - - // get SessionId - List list = new ArrayList<>( - introductionManager1.getIntroductionMessages(contactId0From1)); - assertEquals(2, list.size()); - assertTrue(list.get(0) instanceof IntroductionRequest); - IntroductionRequest msg = (IntroductionRequest) list.get(0); - SessionId sessionId = msg.getSessionId(); - - // get contact group - Group group = - introductionGroupFactory.createIntroductionGroup(contact1From0); - - // create new message with same SessionId - BdfDictionary d = BdfDictionary.of( - new BdfEntry(TYPE, TYPE_REQUEST), - new BdfEntry(SESSION_ID, sessionId), - new BdfEntry(GROUP_ID, group.getId()), - new BdfEntry(NAME, getRandomString(42)), - new BdfEntry(PUBLIC_KEY, getRandomBytes(MAX_PUBLIC_KEY_LENGTH)) - ); - - // reset request received state - listener1.requestReceived = false; - - // add the message to the queue - MessageSender sender0 = c0.getMessageSender(); - Transaction txn = db0.startTransaction(false); - try { - sender0.sendMessage(txn, d); - db0.commitTransaction(txn); - } finally { - db0.endTransaction(txn); - } - - // actually send message - sync0To1(1, false); - - // make sure it does not arrive - assertFalse(listener1.requestReceived); + // The message was invalid, so no abort message was sent + assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertFalse(listener2.aborted); } @Test @@ -523,34 +460,20 @@ public class IntroductionIntegrationTest eventWaiter.await(TIMEOUT, 1); assertTrue(listener1.requestReceived); - // get database and local group for introducee - Group group1 = introductionGroupFactory.createLocalGroup(); + // get local group for introducee1 + Group group1 = + contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); - // get local session state messages - Map map; - Transaction txn = db1.startTransaction(false); - try { - map = db1.getMessageMetadata(txn, group1.getId()); - db1.commitTransaction(txn); - } finally { - db1.endTransaction(txn); - } // check that we have one session state - assertEquals(1, map.size()); + assertEquals(1, c1.getClientHelper() + .getMessageMetadataAsDictionary(group1.getId()).size()); // introducee1 removes introducer contactManager1.removeContact(contactId0From1); - // get local session state messages again - txn = db1.startTransaction(false); - try { - map = db1.getMessageMetadata(txn, group1.getId()); - db1.commitTransaction(txn); - } finally { - db1.endTransaction(txn); - } // make sure local state got deleted - assertEquals(0, map.size()); + assertEquals(0, c1.getClientHelper() + .getMessageMetadataAsDictionary(group1.getId()).size()); } @Test @@ -567,48 +490,36 @@ public class IntroductionIntegrationTest eventWaiter.await(TIMEOUT, 1); assertTrue(listener1.requestReceived); - // get database and local group for introducee - Group group1 = introductionGroupFactory.createLocalGroup(); + // get local group for introducer + Group group0 = + contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); - // get local session state messages - Map map; - Transaction txn = db0.startTransaction(false); - try { - map = db0.getMessageMetadata(txn, group1.getId()); - db0.commitTransaction(txn); - } finally { - db0.endTransaction(txn); - } // check that we have one session state - assertEquals(1, map.size()); + assertEquals(1, c0.getClientHelper() + .getMessageMetadataAsDictionary(group0.getId()).size()); // introducer removes introducee1 contactManager0.removeContact(contactId1From0); - // get local session state messages again - txn = db0.startTransaction(false); - try { - map = db0.getMessageMetadata(txn, group1.getId()); - db0.commitTransaction(txn); - } finally { - db0.endTransaction(txn); - } // make sure local state is still there - assertEquals(1, map.size()); + assertEquals(1, c0.getClientHelper() + .getMessageMetadataAsDictionary(group0.getId()).size()); + + // ensure introducer has aborted the session + assertTrue(listener0.aborted); + + // sync REQUEST and ABORT message + sync0To2(2, true); + + // ensure introducee2 has aborted the session as well + assertTrue(listener2.aborted); // introducer removes other introducee contactManager0.removeContact(contactId2From0); - // get local session state messages again - txn = db0.startTransaction(false); - try { - map = db0.getMessageMetadata(txn, group1.getId()); - db0.commitTransaction(txn); - } finally { - db0.endTransaction(txn); - } // make sure local state is gone now - assertEquals(0, map.size()); + assertEquals(0, c0.getClientHelper() + .getMessageMetadataAsDictionary(group0.getId()).size()); } private void testModifiedResponse(StateVisitor visitor) @@ -630,26 +541,36 @@ public class IntroductionIntegrationTest eventWaiter.await(TIMEOUT, 1); // get response to be forwarded - ClientHelper ch = c0.getClientHelper(); // need 0's ClientHelper here - Entry resp = - getMessageFor(ch, contact2From0, TYPE_RESPONSE); - MessageId responseId = resp.getKey(); - BdfDictionary response = resp.getValue(); - - // adapt outgoing message queue to removed message - Group g2 = introductionGroupFactory - .createIntroductionGroup(contact2From0); - decreaseOutgoingMessageCounter(ch, g2.getId()); + AcceptMessage message = + (AcceptMessage) getMessageFor(c0.getClientHelper(), + contact2From0, ACCEPT); // allow visitor to modify response - boolean earlyAbort = visitor.visit(response); + AcceptMessage m = visitor.visit(message); // replace original response with modified one - MessageSender sender0 = c0.getMessageSender(); Transaction txn = db0.startTransaction(false); try { - db0.deleteMessage(txn, responseId); - sender0.sendMessage(txn, response); + db0.removeMessage(txn, message.getMessageId()); + Message msg = c0.getMessageEncoder() + .encodeAcceptMessage(m.getGroupId(), m.getTimestamp(), + m.getPreviousMessageId(), m.getSessionId(), + m.getEphemeralPublicKey(), m.getAcceptTimestamp(), + m.getTransportProperties()); + c0.getClientHelper() + .addLocalMessage(txn, msg, new BdfDictionary(), true); + Group group0 = contactGroupFactory + .createLocalGroup(CLIENT_ID, CLIENT_VERSION); + BdfDictionary query = BdfDictionary.of( + new BdfEntry(SESSION_KEY_SESSION_ID, m.getSessionId()) + ); + Map.Entry session = c0.getClientHelper() + .getMessageMetadataAsDictionary(txn, group0.getId(), query) + .entrySet().iterator().next(); + replacePreviousLocalMessageId(contact2From0.getAuthor(), + session.getValue(), msg.getId()); + c0.getClientHelper().mergeMessageMetadata(txn, session.getKey(), + session.getValue()); db0.commitTransaction(txn); } finally { db0.endTransaction(txn); @@ -663,21 +584,14 @@ public class IntroductionIntegrationTest sync0To1(1, true); sync0To2(1, true); - // sync first ACK and forward it + // sync first AUTH and forward it sync1To0(1, true); sync0To2(1, true); // introducee2 should have detected the fake now - // and deleted introducee1 again - Collection contacts2; - txn = db2.startTransaction(true); - try { - contacts2 = db2.getContacts(txn); - db2.commitTransaction(txn); - } finally { - db2.endTransaction(txn); - } - assertEquals(1, contacts2.size()); + assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertTrue(listener2.aborted); // sync introducee2's ack and following abort sync2To0(2, true); @@ -687,144 +601,44 @@ public class IntroductionIntegrationTest // sync abort messages to introducees sync0To1(2, true); - sync0To2(1, true); - if (earlyAbort) { - assertTrue(listener1.aborted); - assertTrue(listener2.aborted); - } else { - assertTrue(listener2.aborted); - // when aborted late, introducee1 keeps the contact, - // so introducer can not make contacts disappear by aborting - Collection contacts1; - txn = db1.startTransaction(true); - try { - contacts1 = db1.getContacts(txn); - db1.commitTransaction(txn); - } finally { - db1.endTransaction(txn); - } - assertEquals(2, contacts1.size()); - } + // ensure everybody got the abort now + assertTrue(listener0.aborted); + assertTrue(listener1.aborted); + assertTrue(listener2.aborted); } @Test public void testModifiedTransportProperties() throws Exception { - testModifiedResponse(response -> { - BdfDictionary tp = response.getDictionary(TRANSPORT, null); - tp.put("fakeId", BdfDictionary.of(new BdfEntry("fake", "fake"))); - response.put(TRANSPORT, tp); - return false; - }); + testModifiedResponse( + m -> new AcceptMessage(m.getMessageId(), m.getGroupId(), + m.getTimestamp(), m.getPreviousMessageId(), + m.getSessionId(), m.getEphemeralPublicKey(), + m.getAcceptTimestamp(), + getTransportPropertiesMap(2)) + ); } @Test public void testModifiedTimestamp() throws Exception { - testModifiedResponse(response -> { - long timestamp = response.getLong(TIME, 0L); - response.put(TIME, timestamp + 1); - return false; - }); + testModifiedResponse( + m -> new AcceptMessage(m.getMessageId(), m.getGroupId(), + m.getTimestamp(), m.getPreviousMessageId(), + m.getSessionId(), m.getEphemeralPublicKey(), + clock.currentTimeMillis(), + m.getTransportProperties()) + ); } @Test public void testModifiedEphemeralPublicKey() throws Exception { - testModifiedResponse(response -> { - KeyPair keyPair = crypto.generateAgreementKeyPair(); - response.put(E_PUBLIC_KEY, keyPair.getPublic().getEncoded()); - return true; - }); - } - - @Test - public void testModifiedEphemeralPublicKeyWithFakeMac() - throws Exception { - // initialize a real introducee manager - MessageSender messageSender = c2.getMessageSender(); - TransportPropertyManager tpManager = c2.getTransportPropertyManager(); - IntroduceeManager manager2 = - new IntroduceeManager(messageSender, db2, clientHelper, clock, - crypto, tpManager, authorFactory, contactManager2, - identityManager2, introductionGroupFactory); - - // create keys - KeyPair keyPair1 = crypto.generateSignatureKeyPair(); - KeyPair eKeyPair1 = crypto.generateAgreementKeyPair(); - KeyPair eKeyPair2 = crypto.generateAgreementKeyPair(); - - // Nonce 1 - byte[][] inputs = { - new byte[] {CLIENT_VERSION}, - eKeyPair1.getPublic().getEncoded(), - eKeyPair2.getPublic().getEncoded() - }; - SecretKey sharedSecret = crypto.deriveSharedSecret(SHARED_SECRET_LABEL, - eKeyPair2.getPublic(), eKeyPair1, inputs); - byte[] nonce1 = crypto.mac(ALICE_NONCE_LABEL, sharedSecret); - - // Signature 1 - byte[] sig1 = crypto.sign(SIGNING_LABEL, nonce1, - keyPair1.getPrivate().getEncoded()); - - // MAC 1 - SecretKey macKey1 = crypto.deriveKey(ALICE_MAC_KEY_LABEL, sharedSecret); - BdfDictionary tp1 = BdfDictionary.of(new BdfEntry("fake", "fake")); - long time1 = clock.currentTimeMillis(); - BdfList toMacList = BdfList.of(keyPair1.getPublic().getEncoded(), - eKeyPair1.getPublic().getEncoded(), tp1, time1); - byte[] toMac = clientHelper.toByteArray(toMacList); - byte[] mac1 = crypto.mac(MAC_LABEL, macKey1, toMac); - - // create only relevant part of state for introducee2 - BdfDictionary state = new BdfDictionary(); - state.put(PUBLIC_KEY, keyPair1.getPublic().getEncoded()); - state.put(TRANSPORT, tp1); - state.put(TIME, time1); - state.put(E_PUBLIC_KEY, eKeyPair1.getPublic().getEncoded()); - state.put(MAC, mac1); - state.put(MAC_KEY, macKey1.getBytes()); - state.put(NONCE, nonce1); - state.put(SIGNATURE, sig1); - - // MAC and signature verification should pass - manager2.verifyMac(state); - manager2.verifySignature(state); - - // replace ephemeral key pair and recalculate matching keys and nonce - KeyPair eKeyPair1f = crypto.generateAgreementKeyPair(); - byte[][] fakeInputs = { - new byte[] {CLIENT_VERSION}, - eKeyPair1f.getPublic().getEncoded(), - eKeyPair2.getPublic().getEncoded() - }; - sharedSecret = crypto.deriveSharedSecret(SHARED_SECRET_LABEL, - eKeyPair2.getPublic(), eKeyPair1f, fakeInputs); - nonce1 = crypto.mac(ALICE_NONCE_LABEL, sharedSecret); - - // recalculate MAC - macKey1 = crypto.deriveKey(ALICE_MAC_KEY_LABEL, sharedSecret); - toMacList = BdfList.of(keyPair1.getPublic().getEncoded(), - eKeyPair1f.getPublic().getEncoded(), tp1, time1); - toMac = clientHelper.toByteArray(toMacList); - mac1 = crypto.mac(MAC_LABEL, macKey1, toMac); - - // update state with faked information - state.put(E_PUBLIC_KEY, eKeyPair1f.getPublic().getEncoded()); - state.put(MAC, mac1); - state.put(MAC_KEY, macKey1.getBytes()); - state.put(NONCE, nonce1); - - // MAC verification should still pass - manager2.verifyMac(state); - - // Signature can not be verified, because we don't have private - // long-term key to fake it - try { - manager2.verifySignature(state); - fail(); - } catch (GeneralSecurityException e) { - // expected - } + testModifiedResponse( + m -> new AcceptMessage(m.getMessageId(), m.getGroupId(), + m.getTimestamp(), m.getPreviousMessageId(), + m.getSessionId(), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + m.getAcceptTimestamp(), m.getTransportProperties()) + ); } private void addTransportProperties() @@ -832,17 +646,15 @@ public class IntroductionIntegrationTest TransportPropertyManager tpm0 = c0.getTransportPropertyManager(); TransportPropertyManager tpm1 = c1.getTransportPropertyManager(); TransportPropertyManager tpm2 = c2.getTransportPropertyManager(); - TransportProperties tp = new TransportProperties( - Collections.singletonMap("key", "value")); - tpm0.mergeLocalProperties(TRANSPORT_ID, tp); + tpm0.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2)); sync0To1(1, true); sync0To2(1, true); - tpm1.mergeLocalProperties(TRANSPORT_ID, tp); + tpm1.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2)); sync1To0(1, true); - tpm2.mergeLocalProperties(TRANSPORT_ID, tp); + tpm2.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2)); sync2To0(1, true); } @@ -935,7 +747,7 @@ public class IntroductionIntegrationTest time); } } - } catch (DbException | FormatException exception) { + } catch (DbException exception) { eventWaiter.rethrow(exception); } finally { eventWaiter.resume(); @@ -945,7 +757,6 @@ public class IntroductionIntegrationTest Contact contact = ((IntroductionSucceededEvent) e).getContact(); eventWaiter .assertFalse(contact.getId().equals(contactId0From1)); - eventWaiter.assertTrue(contact.isActive()); eventWaiter.resume(); } else if (e instanceof IntroductionAbortedEvent) { aborted = true; @@ -981,30 +792,41 @@ public class IntroductionIntegrationTest } - private void decreaseOutgoingMessageCounter(ClientHelper ch, GroupId g) - throws FormatException, DbException { - BdfDictionary gD = ch.getGroupMetadataAsDictionary(g); - LOG.warning(gD.toString()); - BdfDictionary queue = gD.getDictionary(QUEUE_STATE_KEY); - queue.put("nextOut", queue.getLong("nextOut") - 1); - gD.put(QUEUE_STATE_KEY, queue); - ch.mergeGroupMetadata(g, gD); + private void replacePreviousLocalMessageId(Author author, + BdfDictionary d, MessageId id) throws FormatException { + BdfDictionary i1 = d.getDictionary(SESSION_KEY_INTRODUCEE_1); + BdfDictionary i2 = d.getDictionary(SESSION_KEY_INTRODUCEE_2); + Author a1 = clientHelper + .parseAndValidateAuthor(i1.getList(SESSION_KEY_AUTHOR)); + Author a2 = clientHelper + .parseAndValidateAuthor(i2.getList(SESSION_KEY_AUTHOR)); + + if (a1.equals(author)) { + i1.put(SESSION_KEY_LAST_LOCAL_MESSAGE_ID, id); + d.put(SESSION_KEY_INTRODUCEE_1, i1); + } else if (a2.equals(author)) { + i2.put(SESSION_KEY_LAST_LOCAL_MESSAGE_ID, id); + d.put(SESSION_KEY_INTRODUCEE_2, i2); + } else { + throw new AssertionError(); + } } - private Entry getMessageFor(ClientHelper ch, - Contact contact, long type) throws FormatException, DbException { - Entry response = null; - Group g = introductionGroupFactory - .createIntroductionGroup(contact); + private AbstractIntroductionMessage getMessageFor(ClientHelper ch, + Contact contact, MessageType type) + throws FormatException, DbException { + Group g = introductionManager0.getContactGroup(contact); + BdfDictionary query = BdfDictionary.of( + new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue()) + ); Map map = - ch.getMessageMetadataAsDictionary(g.getId()); - for (Entry entry : map.entrySet()) { - if (entry.getValue().getLong(TYPE) == type) { - response = entry; - } - } - assertTrue(response != null); - return response; + ch.getMessageMetadataAsDictionary(g.getId(), query); + assertEquals(1, map.size()); + MessageId id = map.entrySet().iterator().next().getKey(); + Message m = ch.getMessage(id); + BdfList body = ch.getMessageAsList(id); + //noinspection ConstantConditions + return c0.getMessageParser().parseAcceptMessage(m, body); } } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java index bc0ea6241..a46d37cbe 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java @@ -59,6 +59,7 @@ interface IntroductionIntegrationTestComponent void inject(IntroductionIntegrationTest init); - MessageSender getMessageSender(); + MessageEncoder getMessageEncoder(); + MessageParser getMessageParser(); } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionManagerImplTest.java deleted file mode 100644 index e44591020..000000000 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionManagerImplTest.java +++ /dev/null @@ -1,291 +0,0 @@ -package org.briarproject.briar.introduction; - -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.client.ClientHelper; -import org.briarproject.bramble.api.contact.Contact; -import org.briarproject.bramble.api.contact.ContactId; -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.data.MetadataParser; -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.AuthorId; -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.sync.MessageStatus; -import org.briarproject.briar.api.client.MessageTracker; -import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.test.BriarTestCase; -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.jmock.lib.legacy.ClassImposteriser; -import org.junit.Test; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; -import static org.briarproject.bramble.test.TestUtils.getAuthor; -import static org.briarproject.bramble.test.TestUtils.getGroup; -import static org.briarproject.bramble.test.TestUtils.getRandomBytes; -import static org.briarproject.bramble.test.TestUtils.getRandomId; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; -import static org.junit.Assert.assertFalse; - -public class IntroductionManagerImplTest extends BriarTestCase { - - private final Mockery context; - private final IntroductionManagerImpl introductionManager; - private final IntroducerManager introducerManager; - private final IntroduceeManager introduceeManager; - private final DatabaseComponent db; - private final ClientHelper clientHelper; - private final MessageTracker messageTracker; - private final IntroductionGroupFactory introductionGroupFactory; - private final SessionId sessionId = new SessionId(getRandomId()); - private final MessageId storageId = new MessageId(sessionId.getBytes()); - private final long time = 42L; - private final Contact introducee1; - private final Contact introducee2; - private final Group introductionGroup1; - private final Group introductionGroup2; - private final Message message1; - private Transaction txn; - - public IntroductionManagerImplTest() { - Author author1 = getAuthor(); - AuthorId localAuthorId1 = new AuthorId(getRandomId()); - ContactId contactId1 = new ContactId(234); - introducee1 = - new Contact(contactId1, author1, localAuthorId1, true, true); - - Author author2 = getAuthor(); - AuthorId localAuthorId2 = new AuthorId(getRandomId()); - ContactId contactId2 = new ContactId(235); - introducee2 = - new Contact(contactId2, author2, localAuthorId2, true, true); - - introductionGroup1 = getGroup(CLIENT_ID); - introductionGroup2 = getGroup(CLIENT_ID); - - message1 = new Message( - new MessageId(getRandomId()), - introductionGroup1.getId(), - time, - getRandomBytes(MESSAGE_HEADER_LENGTH + 1) - ); - - // mock ALL THE THINGS!!! - context = new Mockery(); - context.setImposteriser(ClassImposteriser.INSTANCE); - introducerManager = context.mock(IntroducerManager.class); - introduceeManager = context.mock(IntroduceeManager.class); - db = context.mock(DatabaseComponent.class); - clientHelper = context.mock(ClientHelper.class); - MetadataParser metadataParser = context.mock(MetadataParser.class); - messageTracker = context.mock(MessageTracker.class); - introductionGroupFactory = context.mock(IntroductionGroupFactory.class); - - introductionManager = new IntroductionManagerImpl(db, clientHelper, - metadataParser, messageTracker, introducerManager, - introduceeManager, introductionGroupFactory); - } - - @Test - public void testMakeIntroduction() throws DbException, FormatException { - txn = new Transaction(null, false); - - context.checking(new Expectations() {{ - oneOf(db).startTransaction(false); - will(returnValue(txn)); - oneOf(introducerManager) - .makeIntroduction(txn, introducee1, introducee2, null, - time); - // get both introduction groups - oneOf(introductionGroupFactory) - .createIntroductionGroup(introducee1); - will(returnValue(introductionGroup1)); - oneOf(introductionGroupFactory) - .createIntroductionGroup(introducee2); - will(returnValue(introductionGroup2)); - // track message for group 1 - oneOf(messageTracker).trackMessage(txn, - introductionGroup1.getId(), time, true); - // track message for group 2 - oneOf(messageTracker).trackMessage(txn, - introductionGroup2.getId(), time, true); - oneOf(db).commitTransaction(txn); - oneOf(db).endTransaction(txn); - }}); - - introductionManager - .makeIntroduction(introducee1, introducee2, null, time); - - context.assertIsSatisfied(); - } - - @Test - public void testAcceptIntroduction() throws DbException, FormatException { - BdfDictionary state = BdfDictionary.of( - new BdfEntry(GROUP_ID_1, introductionGroup1.getId()), - new BdfEntry(GROUP_ID_2, introductionGroup2.getId()) - ); - txn = new Transaction(null, false); - - context.checking(new Expectations() {{ - oneOf(db).startTransaction(false); - will(returnValue(txn)); - oneOf(db).getContact(txn, introducee1.getId()); - will(returnValue(introducee1)); - oneOf(introductionGroupFactory).createIntroductionGroup(introducee1); - will(returnValue(introductionGroup1)); - oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId); - will(returnValue(state)); - oneOf(introduceeManager).acceptIntroduction(txn, state, time); - // track message - oneOf(messageTracker).trackMessage(txn, - introductionGroup1.getId(), time, true); - oneOf(db).commitTransaction(txn); - oneOf(db).endTransaction(txn); - }}); - - introductionManager - .acceptIntroduction(introducee1.getId(), sessionId, time); - - context.assertIsSatisfied(); - } - - @Test - public void testDeclineIntroduction() throws DbException, FormatException { - BdfDictionary state = BdfDictionary.of( - new BdfEntry(GROUP_ID_1, introductionGroup1.getId()), - new BdfEntry(GROUP_ID_2, introductionGroup2.getId()) - ); - txn = new Transaction(null, false); - - context.checking(new Expectations() {{ - oneOf(db).startTransaction(false); - will(returnValue(txn)); - oneOf(db).getContact(txn, introducee1.getId()); - will(returnValue(introducee1)); - oneOf(introductionGroupFactory).createIntroductionGroup(introducee1); - will(returnValue(introductionGroup1)); - oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId); - will(returnValue(state)); - oneOf(introduceeManager).declineIntroduction(txn, state, time); - // track message - oneOf(messageTracker).trackMessage(txn, - introductionGroup1.getId(), time, true); - oneOf(db).commitTransaction(txn); - oneOf(db).endTransaction(txn); - }}); - - introductionManager - .declineIntroduction(introducee1.getId(), sessionId, time); - - context.assertIsSatisfied(); - } - - @Test - public void testGetIntroductionMessages() - throws DbException, FormatException { - - Map metadata = Collections.emptyMap(); - Collection statuses = Collections.emptyList(); - txn = new Transaction(null, false); - - context.checking(new Expectations() {{ - oneOf(db).startTransaction(true); - will(returnValue(txn)); - oneOf(db).getContact(txn, introducee1.getId()); - will(returnValue(introducee1)); - oneOf(introductionGroupFactory).createIntroductionGroup(introducee1); - will(returnValue(introductionGroup1)); - oneOf(clientHelper).getMessageMetadataAsDictionary(txn, - introductionGroup1.getId()); - will(returnValue(metadata)); - oneOf(db).getMessageStatus(txn, introducee1.getId(), - introductionGroup1.getId()); - will(returnValue(statuses)); - oneOf(db).commitTransaction(txn); - oneOf(db).endTransaction(txn); - }}); - - introductionManager.getIntroductionMessages(introducee1.getId()); - - context.assertIsSatisfied(); - } - - @Test - public void testIncomingRequestMessage() - throws DbException, FormatException { - - BdfDictionary msg = new BdfDictionary(); - msg.put(TYPE, TYPE_REQUEST); - - BdfDictionary state = new BdfDictionary(); - txn = new Transaction(null, false); - - context.checking(new Expectations() {{ - oneOf(introduceeManager) - .initialize(txn, introductionGroup1.getId(), msg); - will(returnValue(state)); - oneOf(introduceeManager) - .incomingMessage(txn, state, msg); - // track message - oneOf(messageTracker).trackIncomingMessage(txn, message1); - }}); - - introductionManager - .incomingMessage(txn, message1, new BdfList(), msg); - - context.assertIsSatisfied(); - assertFalse(txn.isCommitted()); - } - - @Test - public void testIncomingResponseMessage() - throws DbException, FormatException { - - BdfDictionary msg = BdfDictionary.of( - new BdfEntry(TYPE, TYPE_RESPONSE), - new BdfEntry(SESSION_ID, sessionId) - ); - - BdfDictionary state = new BdfDictionary(); - state.put(ROLE, ROLE_INTRODUCER); - state.put(GROUP_ID_1, introductionGroup1.getId()); - state.put(GROUP_ID_2, introductionGroup2.getId()); - - txn = new Transaction(null, false); - - context.checking(new Expectations() {{ - oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId); - will(returnValue(state)); - oneOf(introducerManager).incomingMessage(txn, state, msg); - // track message - oneOf(messageTracker).trackIncomingMessage(txn, message1); - }}); - - introductionManager - .incomingMessage(txn, message1, new BdfList(), msg); - - context.assertIsSatisfied(); - assertFalse(txn.isCommitted()); - } - - -} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java index a2d481547..4629a1f73 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java @@ -1,361 +1,424 @@ package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.BdfMessageContext; 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.data.MetadataEncoder; -import org.briarproject.bramble.api.identity.Author; -import org.briarproject.bramble.api.plugin.TransportId; -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.bramble.system.SystemClock; +import org.briarproject.bramble.test.ValidatorTestCase; import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.test.BriarTestCase; -import org.jmock.Mockery; +import org.jmock.Expectations; import org.junit.Test; -import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; -import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import javax.annotation.Nullable; + +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.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; -import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; -import static org.briarproject.bramble.test.TestUtils.getAuthor; -import static org.briarproject.bramble.test.TestUtils.getClientId; -import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.util.StringUtils.getRandomString; -import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG; -import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; -import static org.junit.Assert.assertArrayEquals; +import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; +import static org.briarproject.briar.introduction.MessageType.ABORT; +import static org.briarproject.briar.introduction.MessageType.ACCEPT; +import static org.briarproject.briar.introduction.MessageType.ACTIVATE; +import static org.briarproject.briar.introduction.MessageType.AUTH; +import static org.briarproject.briar.introduction.MessageType.DECLINE; +import static org.briarproject.briar.introduction.MessageType.REQUEST; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -public class IntroductionValidatorTest extends BriarTestCase { +public class IntroductionValidatorTest extends ValidatorTestCase { - private final Mockery context = new Mockery(); - private final Group group; - private final Message message; - private final IntroductionValidator validator; - private final Clock clock = new SystemClock(); + private final MessageEncoder messageEncoder = + context.mock(MessageEncoder.class); + private final IntroductionValidator validator = + new IntroductionValidator(messageEncoder, clientHelper, + metadataEncoder, clock); - public IntroductionValidatorTest() { - group = getGroup(getClientId()); - MessageId messageId = new MessageId(getRandomId()); - long timestamp = System.currentTimeMillis(); - byte[] raw = getRandomBytes(123); - message = new Message(messageId, group.getId(), timestamp, raw); - - - ClientHelper clientHelper = context.mock(ClientHelper.class); - MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class); - validator = new IntroductionValidator(clientHelper, metadataEncoder, - clock); - context.assertIsSatisfied(); - } + private final SessionId sessionId = new SessionId(getRandomId()); + private final MessageId previousMsgId = new MessageId(getRandomId()); + private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH); + private final BdfDictionary meta = new BdfDictionary(); + private final long acceptTimestamp = 42; + private final BdfDictionary transportProperties = BdfDictionary.of( + new BdfEntry("transportId", new BdfDictionary()) + ); + private final byte[] mac = getRandomBytes(MAC_BYTES); + private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES); // - // Introduction Requests + // Introduction REQUEST // @Test - public void testValidateProperIntroductionRequest() throws Exception { - byte[] sessionId = getRandomId(); - String name = getRandomString(MAX_AUTHOR_NAME_LENGTH); - byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); - String text = getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH); + public void testAcceptsRequest() throws Exception { + BdfList body = BdfList.of(REQUEST.getValue(), previousMsgId.getBytes(), + authorList, text); - BdfList body = BdfList.of(TYPE_REQUEST, sessionId, - name, publicKey, text); + expectParseAuthor(authorList, author); + expectEncodeRequestMetadata(); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); - BdfDictionary result = - validator.validateMessage(message, group, body).getDictionary(); - - assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE)); - assertEquals(sessionId, result.getRaw(SESSION_ID)); - assertEquals(name, result.getString(NAME)); - assertEquals(publicKey, result.getRaw(PUBLIC_KEY)); - assertEquals(text, result.getString(MSG)); - context.assertIsSatisfied(); - } - - @Test(expected = FormatException.class) - public void testValidateIntroductionRequestWithNoName() throws Exception { - BdfDictionary msg = getValidIntroductionRequest(); - - // no NAME is message - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), - msg.getRaw(PUBLIC_KEY)); - if (msg.containsKey(MSG)) body.add(msg.getString(MSG)); - - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testValidateIntroductionRequestWithLongName() throws Exception { - // too long NAME in message - BdfDictionary msg = getValidIntroductionRequest(); - msg.put(NAME, msg.get(NAME) + "x"); - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), - msg.getString(NAME), msg.getRaw(PUBLIC_KEY)); - if (msg.containsKey(MSG)) body.add(msg.getString(MSG)); - - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testValidateIntroductionRequestWithWrongType() - throws Exception { - // wrong message type - BdfDictionary msg = getValidIntroductionRequest(); - msg.put(TYPE, 324234); - - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), - msg.getString(NAME), msg.getRaw(PUBLIC_KEY)); - if (msg.containsKey(MSG)) body.add(msg.getString(MSG)); - validator.validateMessage(message, group, body); - } - - private BdfDictionary getValidIntroductionRequest() throws Exception { - byte[] sessionId = getRandomId(); - Author author = getAuthor(); - String text = getRandomString(MAX_MESSAGE_BODY_LENGTH); - - BdfDictionary msg = new BdfDictionary(); - msg.put(TYPE, TYPE_REQUEST); - msg.put(SESSION_ID, sessionId); - msg.put(NAME, author.getName()); - msg.put(PUBLIC_KEY, author.getPublicKey()); - msg.put(MSG, text); - - return msg; - } - - // - // Introduction Responses - // - - @Test - public void testValidateIntroductionAcceptResponse() throws Exception { - byte[] groupId = getRandomId(); - byte[] sessionId = getRandomId(); - long time = clock.currentTimeMillis(); - byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES); - String transportId = - getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH); - BdfDictionary tProps = BdfDictionary.of( - new BdfEntry(getRandomString(MAX_PROPERTY_LENGTH), - getRandomString(MAX_PROPERTY_LENGTH)) - ); - BdfDictionary tp = BdfDictionary.of( - new BdfEntry(transportId, tProps) - ); - - BdfDictionary msg = new BdfDictionary(); - msg.put(TYPE, TYPE_RESPONSE); - msg.put(GROUP_ID, groupId); - msg.put(SESSION_ID, sessionId); - msg.put(ACCEPT, true); - msg.put(TIME, time); - msg.put(E_PUBLIC_KEY, publicKey); - msg.put(TRANSPORT, tp); - - BdfList body = BdfList.of(TYPE_RESPONSE, msg.getRaw(SESSION_ID), - msg.getBoolean(ACCEPT), msg.getLong(TIME), - msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT)); - - BdfDictionary result = - validator.validateMessage(message, group, body).getDictionary(); - - assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE)); - assertEquals(sessionId, result.getRaw(SESSION_ID)); - assertEquals(true, result.getBoolean(ACCEPT)); - assertEquals(publicKey, result.getRaw(E_PUBLIC_KEY)); - assertEquals(tp, result.getDictionary(TRANSPORT)); - context.assertIsSatisfied(); + assertExpectedContext(messageContext, previousMsgId); } @Test - public void testValidateIntroductionDeclineResponse() throws Exception { - BdfDictionary msg = getValidIntroductionResponse(false); - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), - msg.getBoolean(ACCEPT)); + public void testAcceptsRequestWithPreviousMsgIdNull() throws Exception { + BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, text); - BdfDictionary result = validator.validateMessage(message, group, body) - .getDictionary(); + expectParseAuthor(authorList, author); + expectEncodeRequestMetadata(); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); - assertFalse(result.getBoolean(ACCEPT)); - context.assertIsSatisfied(); + assertExpectedContext(messageContext, null); + } + + @Test + public void testAcceptsRequestWithMessageNull() throws Exception { + BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, null); + + expectParseAuthor(authorList, author); + expectEncodeRequestMetadata(); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, null); } @Test(expected = FormatException.class) - public void testValidateIntroductionResponseWithoutAccept() - throws Exception { - BdfDictionary msg = getValidIntroductionResponse(false); - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID)); - + public void testRejectsTooShortBodyForRequest() throws Exception { + BdfList body = BdfList.of(REQUEST.getValue(), null, authorList); validator.validateMessage(message, group, body); } @Test(expected = FormatException.class) - public void testValidateIntroductionResponseWithBrokenTp() - throws Exception { - BdfDictionary msg = getValidIntroductionResponse(true); - BdfDictionary tp = msg.getDictionary(TRANSPORT); - tp.put( - getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH), "X"); - msg.put(TRANSPORT, tp); - - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), - msg.getBoolean(ACCEPT), msg.getLong(TIME), - msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT)); - + public void testRejectsTooLongBodyForRequest() throws Exception { + BdfList body = + BdfList.of(REQUEST.getValue(), null, authorList, text, null); validator.validateMessage(message, group, body); } @Test(expected = FormatException.class) - public void testValidateIntroductionResponseWithoutPublicKey() - throws Exception { - BdfDictionary msg = getValidIntroductionResponse(true); - - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), - msg.getBoolean(ACCEPT), msg.getLong(TIME), - msg.getDictionary(TRANSPORT)); - + public void testRejectsRawMessageForRequest() throws Exception { + BdfList body = + BdfList.of(REQUEST.getValue(), null, authorList, getRandomId()); + expectParseAuthor(authorList, author); validator.validateMessage(message, group, body); } - private BdfDictionary getValidIntroductionResponse(boolean accept) + @Test(expected = FormatException.class) + public void testRejectsStringMessageIdForRequest() throws Exception { + BdfList body = + BdfList.of(REQUEST.getValue(), "NoMessageId", authorList, null); + validator.validateMessage(message, group, body); + } + + // + // Introduction ACCEPT + // + + @Test + public void testAcceptsAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + acceptTimestamp, transportProperties); + context.checking(new Expectations() {{ + oneOf(clientHelper).parseAndValidateTransportProperties( + transportProperties.getDictionary("transportId")); + }}); + expectEncodeMetadata(ACCEPT); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + acceptTimestamp, transportProperties, null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidSessionIdForAccept() throws Exception { + BdfList body = + BdfList.of(ACCEPT.getValue(), null, previousMsgId.getBytes(), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp, + transportProperties); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidPreviousMsgIdForAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), 1, + getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp, + transportProperties); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongPublicKeyForAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp, + transportProperties); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsEmptyTransportPropertiesForAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), + getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp, + new BdfDictionary()); + validator.validateMessage(message, group, body); + } - byte[] groupId = getRandomId(); - byte[] sessionId = getRandomId(); - long time = clock.currentTimeMillis(); - byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES); - String transportId = - getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH); - BdfDictionary tProps = BdfDictionary.of( - new BdfEntry(getRandomString(MAX_PROPERTY_LENGTH), - getRandomString(MAX_PROPERTY_LENGTH)) - ); - BdfDictionary tp = BdfDictionary.of( - new BdfEntry(transportId, tProps) - ); + // + // Introduction DECLINE + // - BdfDictionary msg = new BdfDictionary(); - msg.put(TYPE, TYPE_RESPONSE); - msg.put(GROUP_ID, groupId); - msg.put(SESSION_ID, sessionId); - msg.put(ACCEPT, accept); - if (accept) { - msg.put(TIME, time); - msg.put(E_PUBLIC_KEY, publicKey); - msg.put(TRANSPORT, tp); + @Test + public void testAcceptsDecline() throws Exception { + BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), + previousMsgId.getBytes()); + + expectEncodeMetadata(DECLINE); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForDecline() throws Exception { + BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForDecline() throws Exception { + BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidSessionIdForDecline() throws Exception { + BdfList body = + BdfList.of(DECLINE.getValue(), null, previousMsgId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidPreviousMsgIdForDecline() throws Exception { + BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), 1); + validator.validateMessage(message, group, body); + } + + // + // Introduction AUTH + // + + @Test + public void testAcceptsAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac, signature); + + expectEncodeMetadata(AUTH); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac, signature, null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortMacForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), getRandomBytes(MAC_BYTES - 1), + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongMacForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), + getRandomBytes(MAC_BYTES + 1), signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidMacForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), null, signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortSignatureForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac, getRandomBytes(0)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongSignatureForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac, + getRandomBytes(MAX_SIGNATURE_BYTES + 1)); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidSignatureForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), mac, null); + validator.validateMessage(message, group, body); + } + + // + // Introduction ACTIVATE + // + + @Test + public void testAcceptsActivate() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), + previousMsgId.getBytes()); + + expectEncodeMetadata(ACTIVATE); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForActivate() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForActivate() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidSessionIdForActivate() throws Exception { + BdfList body = + BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidPreviousMsgIdForActivate() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), 1); + validator.validateMessage(message, group, body); + } + + // + // Introduction ABORT + // + + @Test + public void testAcceptsAbort() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes()); + + expectEncodeMetadata(ABORT); + BdfMessageContext messageContext = + validator.validateMessage(message, group, body); + + assertExpectedContext(messageContext, previousMsgId); + } + + @Test(expected = FormatException.class) + public void testRejectsTooShortBodyForAbort() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBodyForAbort() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), null); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidSessionIdForAbort() throws Exception { + BdfList body = + BdfList.of(ABORT.getValue(), null, previousMsgId.getBytes()); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidPreviousMsgIdForAbort() throws Exception { + BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), 1); + validator.validateMessage(message, group, body); + } + + // + // Introduction Helper Methods + // + + private void expectEncodeRequestMetadata() { + context.checking(new Expectations() {{ + oneOf(messageEncoder) + .encodeRequestMetadata(message.getTimestamp(), false, false, + false, false); + will(returnValue(meta)); + }}); + } + + private void expectEncodeMetadata(MessageType type) { + context.checking(new Expectations() {{ + oneOf(messageEncoder) + .encodeMetadata(type, sessionId, message.getTimestamp(), + false, false, false); + will(returnValue(meta)); + }}); + } + + private void assertExpectedContext(BdfMessageContext c, + @Nullable MessageId dependency) { + assertEquals(meta, c.getDictionary()); + if (dependency == null) { + assertEquals(0, c.getDependencies().size()); + } else { + assertEquals(dependency, c.getDependencies().iterator().next()); } - - return msg; - } - - // - // Introduction ACK - // - - @Test - public void testValidateProperIntroductionAck() throws Exception { - byte[] sessionId = getRandomId(); - byte[] mac = getRandomBytes(MAC_LENGTH); - byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH); - BdfList body = BdfList.of(TYPE_ACK, sessionId, mac, sig); - - BdfDictionary result = - validator.validateMessage(message, group, body).getDictionary(); - - assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE)); - assertArrayEquals(sessionId, result.getRaw(SESSION_ID)); - assertArrayEquals(mac, result.getRaw(MAC)); - assertArrayEquals(sig, result.getRaw(SIGNATURE)); - context.assertIsSatisfied(); - } - - @Test(expected = FormatException.class) - public void testValidateTooLongIntroductionAck() throws Exception { - BdfDictionary msg = BdfDictionary.of( - new BdfEntry(TYPE, TYPE_ACK), - new BdfEntry(SESSION_ID, getRandomId()), - new BdfEntry("garbage", getRandomString(255)) - ); - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), - msg.getString("garbage")); - - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testValidateIntroductionAckWithLongSessionId() - throws Exception { - BdfDictionary msg = BdfDictionary.of( - new BdfEntry(TYPE, TYPE_ACK), - new BdfEntry(SESSION_ID, new byte[SessionId.LENGTH + 1]) - ); - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID)); - - validator.validateMessage(message, group, body); - } - - // - // Introduction Abort - // - - @Test - public void testValidateProperIntroductionAbort() throws Exception { - byte[] sessionId = getRandomId(); - - BdfDictionary msg = new BdfDictionary(); - msg.put(TYPE, TYPE_ABORT); - msg.put(SESSION_ID, sessionId); - - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID)); - - BdfDictionary result = - validator.validateMessage(message, group, body).getDictionary(); - - assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE)); - assertEquals(sessionId, result.getRaw(SESSION_ID)); - context.assertIsSatisfied(); - } - - @Test(expected = FormatException.class) - public void testValidateTooLongIntroductionAbort() throws Exception { - BdfDictionary msg = BdfDictionary.of( - new BdfEntry(TYPE, TYPE_ABORT), - new BdfEntry(SESSION_ID, getRandomId()), - new BdfEntry("garbage", getRandomString(255)) - ); - BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), - msg.getString("garbage")); - - validator.validateMessage(message, group, body); } } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java similarity index 96% rename from briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java rename to briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java index e623f814a..f8c8fae34 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; @@ -28,9 +28,9 @@ import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; import static org.briarproject.bramble.util.StringUtils.getRandomString; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; -import static org.briarproject.briar.introduction2.MessageType.ABORT; -import static org.briarproject.briar.introduction2.MessageType.REQUEST; +import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; +import static org.briarproject.briar.introduction.MessageType.ABORT; +import static org.briarproject.briar.introduction.MessageType.REQUEST; import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -55,8 +55,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { private final SessionId sessionId = new SessionId(getRandomId()); private final MessageId previousMsgId = new MessageId(getRandomId()); private final Author author; - private final String text = - getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH); + private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH); private final byte[] ephemeralPublicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); private final byte[] mac = getRandomBytes(MAC_BYTES); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java similarity index 90% rename from briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java rename to briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java index c7d60662d..b1dae81c8 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction2/MessageEncoderTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; @@ -16,8 +16,8 @@ import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.util.StringUtils.getRandomString; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; -import static org.briarproject.briar.introduction2.MessageType.REQUEST; +import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; +import static org.briarproject.briar.introduction.MessageType.REQUEST; public class MessageEncoderTest extends BrambleMockTestCase { @@ -35,8 +35,7 @@ public class MessageEncoderTest extends BrambleMockTestCase { private final byte[] body = getRandomBytes(42); private final Author author = getAuthor(); private final BdfList authorList = new BdfList(); - private final String text = - getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH); + private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH); @Test public void testEncodeRequestMessage() throws FormatException { diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageSenderTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageSenderTest.java deleted file mode 100644 index 1f922d706..000000000 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageSenderTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.briarproject.briar.introduction; - -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.data.MetadataEncoder; -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.sync.Group; -import org.briarproject.bramble.api.system.Clock; -import org.briarproject.briar.api.client.MessageQueueManager; -import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.test.BriarTestCase; -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.junit.Test; - -import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; -import static org.briarproject.bramble.test.TestUtils.getClientId; -import static org.briarproject.bramble.test.TestUtils.getGroup; -import static org.briarproject.bramble.test.TestUtils.getRandomBytes; -import static org.briarproject.bramble.test.TestUtils.getRandomId; -import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; -import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; -import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; -import static org.junit.Assert.assertFalse; - -public class MessageSenderTest extends BriarTestCase { - - private final Mockery context; - private final MessageSender messageSender; - private final DatabaseComponent db; - private final ClientHelper clientHelper; - private final MetadataEncoder metadataEncoder; - private final MessageQueueManager messageQueueManager; - private final Clock clock; - - public MessageSenderTest() { - context = new Mockery(); - db = context.mock(DatabaseComponent.class); - clientHelper = context.mock(ClientHelper.class); - metadataEncoder = - context.mock(MetadataEncoder.class); - messageQueueManager = - context.mock(MessageQueueManager.class); - clock = context.mock(Clock.class); - - messageSender = new MessageSender(db, clientHelper, clock, - metadataEncoder, messageQueueManager); - } - - @Test - public void testSendMessage() throws DbException, FormatException { - Transaction txn = new Transaction(null, false); - Group privateGroup = getGroup(getClientId()); - SessionId sessionId = new SessionId(getRandomId()); - byte[] mac = getRandomBytes(42); - byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH); - long time = 42L; - BdfDictionary msg = BdfDictionary.of( - new BdfEntry(TYPE, TYPE_ACK), - new BdfEntry(GROUP_ID, privateGroup.getId()), - new BdfEntry(SESSION_ID, sessionId), - new BdfEntry(MAC, mac), - new BdfEntry(SIGNATURE, sig) - ); - BdfList bodyList = - BdfList.of(TYPE_ACK, sessionId.getBytes(), mac, sig); - byte[] body = getRandomBytes(8); - Metadata metadata = new Metadata(); - - context.checking(new Expectations() {{ - oneOf(clientHelper).toByteArray(bodyList); - will(returnValue(body)); - oneOf(db).getGroup(txn, privateGroup.getId()); - will(returnValue(privateGroup)); - oneOf(metadataEncoder).encode(msg); - will(returnValue(metadata)); - oneOf(clock).currentTimeMillis(); - will(returnValue(time)); - oneOf(messageQueueManager) - .sendMessage(txn, privateGroup, time, body, metadata); - }}); - - messageSender.sendMessage(txn, msg); - - context.assertIsSatisfied(); - assertFalse(txn.isCommitted()); - } - -} diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/SessionEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java similarity index 95% rename from briar-core/src/test/java/org/briarproject/briar/introduction2/SessionEncoderParserIntegrationTest.java rename to briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java index 24ddf208a..00bec26a8 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction2/SessionEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java @@ -1,4 +1,4 @@ -package org.briarproject.briar.introduction2; +package org.briarproject.briar.introduction; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; @@ -13,7 +13,7 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.introduction2.IntroducerSession.Introducee; +import org.briarproject.briar.introduction.IntroducerSession.Introducee; import org.briarproject.briar.test.BriarIntegrationTestComponent; import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent; import org.junit.Test; @@ -29,11 +29,11 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; import static org.briarproject.bramble.util.StringUtils.getRandomString; -import static org.briarproject.briar.introduction2.IntroduceeState.LOCAL_ACCEPTED; -import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTHS; -import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE; -import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; -import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; +import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED; +import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTHS; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE; +import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; +import static org.briarproject.briar.api.introduction.Role.INTRODUCER; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java deleted file mode 100644 index 0fa094a5e..000000000 --- a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionValidatorTest.java +++ /dev/null @@ -1,428 +0,0 @@ -package org.briarproject.briar.introduction2; - -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.client.BdfMessageContext; -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.sync.MessageId; -import org.briarproject.bramble.test.ValidatorTestCase; -import org.briarproject.briar.api.client.SessionId; -import org.jmock.Expectations; -import org.junit.Test; - -import javax.annotation.Nullable; - -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.test.TestUtils.getRandomBytes; -import static org.briarproject.bramble.test.TestUtils.getRandomId; -import static org.briarproject.bramble.util.StringUtils.getRandomString; -import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; -import static org.briarproject.briar.introduction2.MessageType.ABORT; -import static org.briarproject.briar.introduction2.MessageType.ACCEPT; -import static org.briarproject.briar.introduction2.MessageType.ACTIVATE; -import static org.briarproject.briar.introduction2.MessageType.AUTH; -import static org.briarproject.briar.introduction2.MessageType.DECLINE; -import static org.briarproject.briar.introduction2.MessageType.REQUEST; -import static org.junit.Assert.assertEquals; - -public class IntroductionValidatorTest extends ValidatorTestCase { - - private final MessageEncoder messageEncoder = - context.mock(MessageEncoder.class); - private final IntroductionValidator validator = - new IntroductionValidator(messageEncoder, clientHelper, - metadataEncoder, clock); - - private final SessionId sessionId = new SessionId(getRandomId()); - private final MessageId previousMsgId = new MessageId(getRandomId()); - private final String text = - getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH); - private final BdfDictionary meta = new BdfDictionary(); - private final long acceptTimestamp = 42; - private final BdfDictionary transportProperties = BdfDictionary.of( - new BdfEntry("transportId", new BdfDictionary()) - ); - private final byte[] mac = getRandomBytes(MAC_BYTES); - private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES); - - // - // Introduction REQUEST - // - - @Test - public void testAcceptsRequest() throws Exception { - BdfList body = BdfList.of(REQUEST.getValue(), previousMsgId.getBytes(), - authorList, text); - - expectParseAuthor(authorList, author); - expectEncodeRequestMetadata(); - BdfMessageContext messageContext = - validator.validateMessage(message, group, body); - - assertExpectedContext(messageContext, previousMsgId); - } - - @Test - public void testAcceptsRequestWithPreviousMsgIdNull() throws Exception { - BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, text); - - expectParseAuthor(authorList, author); - expectEncodeRequestMetadata(); - BdfMessageContext messageContext = - validator.validateMessage(message, group, body); - - assertExpectedContext(messageContext, null); - } - - @Test - public void testAcceptsRequestWithMessageNull() throws Exception { - BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, null); - - expectParseAuthor(authorList, author); - expectEncodeRequestMetadata(); - BdfMessageContext messageContext = - validator.validateMessage(message, group, body); - - assertExpectedContext(messageContext, null); - } - - @Test(expected = FormatException.class) - public void testRejectsTooShortBodyForRequest() throws Exception { - BdfList body = BdfList.of(REQUEST.getValue(), null, authorList); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooLongBodyForRequest() throws Exception { - BdfList body = - BdfList.of(REQUEST.getValue(), null, authorList, text, null); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsRawMessageForRequest() throws Exception { - BdfList body = - BdfList.of(REQUEST.getValue(), null, authorList, getRandomId()); - expectParseAuthor(authorList, author); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsStringMessageIdForRequest() throws Exception { - BdfList body = - BdfList.of(REQUEST.getValue(), "NoMessageId", authorList, null); - validator.validateMessage(message, group, body); - } - - // - // Introduction ACCEPT - // - - @Test - public void testAcceptsAccept() throws Exception { - BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH), - acceptTimestamp, transportProperties); - context.checking(new Expectations() {{ - oneOf(clientHelper).parseAndValidateTransportProperties( - transportProperties.getDictionary("transportId")); - }}); - expectEncodeMetadata(ACCEPT); - BdfMessageContext messageContext = - validator.validateMessage(message, group, body); - - assertExpectedContext(messageContext, previousMsgId); - } - - @Test(expected = FormatException.class) - public void testRejectsTooShortBodyForAccept() throws Exception { - BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), - getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooLongBodyForAccept() throws Exception { - BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH), - acceptTimestamp, transportProperties, null); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsInvalidSessionIdForAccept() throws Exception { - BdfList body = - BdfList.of(ACCEPT.getValue(), null, previousMsgId.getBytes(), - getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp, - transportProperties); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsInvalidPreviousMsgIdForAccept() throws Exception { - BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), - null, getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp, - transportProperties); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooLongPublicKeyForAccept() throws Exception { - BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), - getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp, - transportProperties); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsEmptyTransportPropertiesForAccept() - throws Exception { - BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), - getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp, - new BdfDictionary()); - validator.validateMessage(message, group, body); - } - - // - // Introduction DECLINE - // - - @Test - public void testAcceptsDecline() throws Exception { - BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), - previousMsgId.getBytes()); - - expectEncodeMetadata(DECLINE); - BdfMessageContext messageContext = - validator.validateMessage(message, group, body); - - assertExpectedContext(messageContext, previousMsgId); - } - - @Test(expected = FormatException.class) - public void testRejectsTooShortBodyForDecline() throws Exception { - BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes()); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooLongBodyForDecline() throws Exception { - BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), null); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsInvalidSessionIdForDecline() throws Exception { - BdfList body = - BdfList.of(DECLINE.getValue(), null, previousMsgId.getBytes()); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsInvalidPreviousMsgIdForDecline() throws Exception { - BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), - null); - validator.validateMessage(message, group, body); - } - - // - // Introduction AUTH - // - - @Test - public void testAcceptsAuth() throws Exception { - BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), mac, signature); - - expectEncodeMetadata(AUTH); - BdfMessageContext messageContext = - validator.validateMessage(message, group, body); - - assertExpectedContext(messageContext, previousMsgId); - } - - @Test(expected = FormatException.class) - public void testRejectsTooShortBodyForAuth() throws Exception { - BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), mac); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooLongBodyForAuth() throws Exception { - BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), mac, signature, null); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooShortMacForAuth() throws Exception { - BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), getRandomBytes(MAC_BYTES - 1), - signature); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooLongMacForAuth() throws Exception { - BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), - getRandomBytes(MAC_BYTES + 1), signature); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsInvalidMacForAuth() throws Exception { - BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), null, signature); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooShortSignatureForAuth() throws Exception { - BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), mac, getRandomBytes(0)); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooLongSignatureForAuth() throws Exception { - BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), mac, - getRandomBytes(MAX_SIGNATURE_BYTES + 1)); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsInvalidSignatureForAuth() throws Exception { - BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), mac, null); - validator.validateMessage(message, group, body); - } - - // - // Introduction ACTIVATE - // - - @Test - public void testAcceptsActivate() throws Exception { - BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), - previousMsgId.getBytes()); - - expectEncodeMetadata(ACTIVATE); - BdfMessageContext messageContext = - validator.validateMessage(message, group, body); - - assertExpectedContext(messageContext, previousMsgId); - } - - @Test(expected = FormatException.class) - public void testRejectsTooShortBodyForActivate() throws Exception { - BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes()); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooLongBodyForActivate() throws Exception { - BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), null); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsInvalidSessionIdForActivate() throws Exception { - BdfList body = - BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes()); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsInvalidPreviousMsgIdForActivate() throws Exception { - BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), - null); - validator.validateMessage(message, group, body); - } - - // - // Introduction ABORT - // - - @Test - public void testAcceptsAbort() throws Exception { - BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), - previousMsgId.getBytes()); - - expectEncodeMetadata(ABORT); - BdfMessageContext messageContext = - validator.validateMessage(message, group, body); - - assertExpectedContext(messageContext, previousMsgId); - } - - @Test(expected = FormatException.class) - public void testRejectsTooShortBodyForAbort() throws Exception { - BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes()); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsTooLongBodyForAbort() throws Exception { - BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), null); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsInvalidSessionIdForAbort() throws Exception { - BdfList body = - BdfList.of(ABORT.getValue(), null, previousMsgId.getBytes()); - validator.validateMessage(message, group, body); - } - - @Test(expected = FormatException.class) - public void testRejectsInvalidPreviousMsgIdForAbort() throws Exception { - BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), - null); - validator.validateMessage(message, group, body); - } - - // - // Introduction Helper Methods - // - - private void expectEncodeRequestMetadata() { - context.checking(new Expectations() {{ - oneOf(messageEncoder) - .encodeRequestMetadata(message.getTimestamp(), false, false, - false, false); - will(returnValue(meta)); - }}); - } - - private void expectEncodeMetadata(MessageType type) { - context.checking(new Expectations() {{ - oneOf(messageEncoder) - .encodeMetadata(type, sessionId, message.getTimestamp(), - false, false, false); - will(returnValue(meta)); - }}); - } - - private void assertExpectedContext(BdfMessageContext c, - @Nullable MessageId dependency) { - assertEquals(meta, c.getDictionary()); - if (dependency == null) { - assertEquals(0, c.getDependencies().size()); - } else { - assertEquals(dependency, c.getDependencies().iterator().next()); - } - } - -} diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index fe2e745c0..a76abd057 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -37,9 +37,9 @@ import org.briarproject.briar.blog.BlogModule; import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.forum.ForumModule; import org.briarproject.briar.introduction.IntroductionModule; -import org.briarproject.briar.introduction2.IntroductionCryptoImplTest; -import org.briarproject.briar.introduction2.MessageEncoderParserIntegrationTest; -import org.briarproject.briar.introduction2.SessionEncoderParserIntegrationTest; +import org.briarproject.briar.introduction.IntroductionCryptoImplTest; +import org.briarproject.briar.introduction.MessageEncoderParserIntegrationTest; +import org.briarproject.briar.introduction.SessionEncoderParserIntegrationTest; import org.briarproject.briar.messaging.MessagingModule; import org.briarproject.briar.privategroup.PrivateGroupModule; import org.briarproject.briar.privategroup.invitation.GroupInvitationModule; From a9b678df32aa2b36141128b63bd23f1960a24019 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 24 Apr 2018 14:12:24 -0300 Subject: [PATCH 09/21] Remove broken and deprecated MessageQueue as it is not needed anymore Closes #308 --- .../briar/api/client/MessageQueueManager.java | 72 --- .../briar/api/client/ProtocolEngine.java | 50 -- .../briar/api/client/QueueMessage.java | 31 - .../briar/api/client/QueueMessageFactory.java | 15 - .../briar/client/BdfIncomingMessageHook.java | 16 +- .../client/BdfQueueMessageValidator.java | 71 --- .../briar/client/BriarClientModule.java | 23 - .../briar/client/MessageQueueManagerImpl.java | 259 -------- .../briar/client/QueueMessageFactoryImpl.java | 60 -- .../client/MessageQueueManagerImplTest.java | 566 ------------------ 10 files changed, 1 insertion(+), 1162 deletions(-) delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/client/MessageQueueManager.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/client/ProtocolEngine.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/client/QueueMessage.java delete mode 100644 briar-api/src/main/java/org/briarproject/briar/api/client/QueueMessageFactory.java delete mode 100644 briar-core/src/main/java/org/briarproject/briar/client/BdfQueueMessageValidator.java delete mode 100644 briar-core/src/main/java/org/briarproject/briar/client/MessageQueueManagerImpl.java delete mode 100644 briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java delete mode 100644 briar-core/src/test/java/org/briarproject/briar/client/MessageQueueManagerImplTest.java diff --git a/briar-api/src/main/java/org/briarproject/briar/api/client/MessageQueueManager.java b/briar-api/src/main/java/org/briarproject/briar/api/client/MessageQueueManager.java deleted file mode 100644 index 75418bf7c..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/client/MessageQueueManager.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.briarproject.briar.api.client; - -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.ClientId; -import org.briarproject.bramble.api.sync.Group; -import org.briarproject.bramble.api.sync.InvalidMessageException; -import org.briarproject.bramble.api.sync.MessageContext; - -@Deprecated -@NotNullByDefault -public interface MessageQueueManager { - - /** - * The key used for storing the queue's state in the group metadata. - */ - String QUEUE_STATE_KEY = "queueState"; - - /** - * Sends a message using the given queue. - */ - QueueMessage sendMessage(Transaction txn, Group queue, long timestamp, - byte[] body, Metadata meta) throws DbException; - - /** - * Sets the message validator for the given client. - */ - void registerMessageValidator(ClientId c, QueueMessageValidator v); - - /** - * Sets the incoming message hook for the given client. The hook will be - * called once for each incoming message that passes validation. Messages - * are passed to the hook in order. - */ - void registerIncomingMessageHook(ClientId c, IncomingQueueMessageHook hook); - - @Deprecated - interface QueueMessageValidator { - - /** - * Validates the given message and returns its metadata and - * dependencies. - */ - MessageContext validateMessage(QueueMessage q, Group g) - throws InvalidMessageException; - } - - @Deprecated - interface IncomingQueueMessageHook { - - /** - * Called once for each incoming message that passes validation. - * Messages are passed to the hook in order. - * - * @throws DbException Should only be used for real database errors. - * If this is thrown, delivery will be attempted again at next startup, - * whereas if an InvalidMessageException is thrown, - * the message will be permanently invalidated. - * @throws InvalidMessageException 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 InvalidMessageException! - */ - void incomingMessage(Transaction txn, QueueMessage q, Metadata meta) - throws DbException, InvalidMessageException; - } -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/client/ProtocolEngine.java b/briar-api/src/main/java/org/briarproject/briar/api/client/ProtocolEngine.java deleted file mode 100644 index 281d9af86..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/client/ProtocolEngine.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.briarproject.briar.api.client; - -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import java.util.List; - -@Deprecated -@NotNullByDefault -public interface ProtocolEngine { - - StateUpdate onLocalAction(S localState, A action); - - StateUpdate onMessageReceived(S localState, M received); - - StateUpdate onMessageDelivered(S localState, M delivered); - - class StateUpdate { - public final boolean deleteMessage; - public final boolean deleteState; - public final S localState; - public final List toSend; - public final List toBroadcast; - - /** - * This class represents an update of the local protocol state. - * It only shows how the state should be updated, - * but does not carry out the updates on its own. - * - * @param deleteMessage whether to delete the message that triggered - * the state update. This will be ignored for - * {@link ProtocolEngine#onLocalAction}. - * @param deleteState whether to delete the localState {@link S} - * @param localState the new local state - * @param toSend a list of messages to be sent as part of the - * state update - * @param toBroadcast a list of events to broadcast as result of the - * state update - */ - public StateUpdate(boolean deleteMessage, boolean deleteState, - S localState, List toSend, List toBroadcast) { - - this.deleteMessage = deleteMessage; - this.deleteState = deleteState; - this.localState = localState; - this.toSend = toSend; - this.toBroadcast = toBroadcast; - } - } -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/client/QueueMessage.java b/briar-api/src/main/java/org/briarproject/briar/api/client/QueueMessage.java deleted file mode 100644 index c41b6da2b..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/client/QueueMessage.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.briarproject.briar.api.client; - -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 static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; -import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; - -@Deprecated -@NotNullByDefault -public class QueueMessage extends Message { - - public static final int QUEUE_MESSAGE_HEADER_LENGTH = - MESSAGE_HEADER_LENGTH + 8; - public static final int MAX_QUEUE_MESSAGE_BODY_LENGTH = - MAX_MESSAGE_BODY_LENGTH - 8; - - private final long queuePosition; - - public QueueMessage(MessageId id, GroupId groupId, long timestamp, - long queuePosition, byte[] raw) { - super(id, groupId, timestamp, raw); - this.queuePosition = queuePosition; - } - - public long getQueuePosition() { - return queuePosition; - } -} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/client/QueueMessageFactory.java b/briar-api/src/main/java/org/briarproject/briar/api/client/QueueMessageFactory.java deleted file mode 100644 index ac458a8a8..000000000 --- a/briar-api/src/main/java/org/briarproject/briar/api/client/QueueMessageFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.briarproject.briar.api.client; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.sync.GroupId; -import org.briarproject.bramble.api.sync.MessageId; - -@Deprecated -@NotNullByDefault -public interface QueueMessageFactory { - - QueueMessage createMessage(GroupId groupId, long timestamp, - long queuePosition, byte[] body); - - QueueMessage createMessage(MessageId id, byte[] raw); -} diff --git a/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java b/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java index b7e16c8d6..17f02ca43 100644 --- a/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java +++ b/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java @@ -13,18 +13,14 @@ 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 org.briarproject.briar.api.client.MessageQueueManager.IncomingQueueMessageHook; -import org.briarproject.briar.api.client.QueueMessage; import javax.annotation.concurrent.Immutable; import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; -import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH; @Immutable @NotNullByDefault -public abstract class BdfIncomingMessageHook implements IncomingMessageHook, - IncomingQueueMessageHook { +public abstract class BdfIncomingMessageHook implements IncomingMessageHook { protected final DatabaseComponent db; protected final ClientHelper clientHelper; @@ -67,16 +63,6 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook, } } - @Override - public void incomingMessage(Transaction txn, QueueMessage q, Metadata meta) - throws DbException, InvalidMessageException { - try { - incomingMessage(txn, q, meta, QUEUE_MESSAGE_HEADER_LENGTH); - } catch (FormatException e) { - throw new InvalidMessageException(e); - } - } - private boolean incomingMessage(Transaction txn, Message m, Metadata meta, int headerLength) throws DbException, FormatException { byte[] raw = m.getRaw(); diff --git a/briar-core/src/main/java/org/briarproject/briar/client/BdfQueueMessageValidator.java b/briar-core/src/main/java/org/briarproject/briar/client/BdfQueueMessageValidator.java deleted file mode 100644 index 48fd665a1..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/client/BdfQueueMessageValidator.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.briarproject.briar.client; - -import org.briarproject.bramble.api.FormatException; -import org.briarproject.bramble.api.client.BdfMessageContext; -import org.briarproject.bramble.api.client.ClientHelper; -import org.briarproject.bramble.api.data.BdfList; -import org.briarproject.bramble.api.data.MetadataEncoder; -import org.briarproject.bramble.api.db.Metadata; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.sync.Group; -import org.briarproject.bramble.api.sync.InvalidMessageException; -import org.briarproject.bramble.api.sync.Message; -import org.briarproject.bramble.api.sync.MessageContext; -import org.briarproject.bramble.api.system.Clock; -import org.briarproject.briar.api.client.MessageQueueManager.QueueMessageValidator; -import org.briarproject.briar.api.client.QueueMessage; - -import java.util.logging.Logger; - -import javax.annotation.concurrent.Immutable; - -import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE; -import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH; - -@Deprecated -@Immutable -@NotNullByDefault -public abstract class BdfQueueMessageValidator - implements QueueMessageValidator { - - protected static final Logger LOG = - Logger.getLogger(BdfQueueMessageValidator.class.getName()); - - protected final ClientHelper clientHelper; - protected final MetadataEncoder metadataEncoder; - protected final Clock clock; - - protected BdfQueueMessageValidator(ClientHelper clientHelper, - MetadataEncoder metadataEncoder, Clock clock) { - this.clientHelper = clientHelper; - this.metadataEncoder = metadataEncoder; - this.clock = clock; - } - - protected abstract BdfMessageContext validateMessage(Message m, Group g, - BdfList body) throws InvalidMessageException, FormatException; - - @Override - public MessageContext validateMessage(QueueMessage q, Group g) - throws InvalidMessageException { - // Reject the message if it's too far in the future - long now = clock.currentTimeMillis(); - if (q.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) { - throw new InvalidMessageException( - "Timestamp is too far in the future"); - } - byte[] raw = q.getRaw(); - if (raw.length <= QUEUE_MESSAGE_HEADER_LENGTH) { - throw new InvalidMessageException("Message is too short"); - } - try { - BdfList body = clientHelper.toList(raw, QUEUE_MESSAGE_HEADER_LENGTH, - raw.length - QUEUE_MESSAGE_HEADER_LENGTH); - BdfMessageContext result = validateMessage(q, g, body); - Metadata meta = metadataEncoder.encode(result.getDictionary()); - return new MessageContext(meta, result.getDependencies()); - } catch (FormatException e) { - throw new InvalidMessageException(e); - } - } -} diff --git a/briar-core/src/main/java/org/briarproject/briar/client/BriarClientModule.java b/briar-core/src/main/java/org/briarproject/briar/client/BriarClientModule.java index 46ee505df..9eeb6b959 100644 --- a/briar-core/src/main/java/org/briarproject/briar/client/BriarClientModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/client/BriarClientModule.java @@ -1,14 +1,6 @@ package org.briarproject.briar.client; -import org.briarproject.bramble.api.client.ClientHelper; -import org.briarproject.bramble.api.db.DatabaseComponent; -import org.briarproject.bramble.api.sync.MessageFactory; -import org.briarproject.bramble.api.sync.ValidationManager; -import org.briarproject.briar.api.client.MessageQueueManager; import org.briarproject.briar.api.client.MessageTracker; -import org.briarproject.briar.api.client.QueueMessageFactory; - -import javax.inject.Singleton; import dagger.Module; import dagger.Provides; @@ -16,21 +8,6 @@ import dagger.Provides; @Module public class BriarClientModule { - @Provides - @Singleton - MessageQueueManager provideMessageQueueManager(DatabaseComponent db, - ClientHelper clientHelper, QueueMessageFactory queueMessageFactory, - ValidationManager validationManager) { - return new MessageQueueManagerImpl(db, clientHelper, - queueMessageFactory, validationManager); - } - - @Provides - QueueMessageFactory provideQueueMessageFactory( - MessageFactory messageFactory) { - return new QueueMessageFactoryImpl(messageFactory); - } - @Provides MessageTracker provideMessageTracker(MessageTrackerImpl messageTracker) { return messageTracker; diff --git a/briar-core/src/main/java/org/briarproject/briar/client/MessageQueueManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/client/MessageQueueManagerImpl.java deleted file mode 100644 index 47c91bbc2..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/client/MessageQueueManagerImpl.java +++ /dev/null @@ -1,259 +0,0 @@ -package org.briarproject.briar.client; - -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.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.ClientId; -import org.briarproject.bramble.api.sync.Group; -import org.briarproject.bramble.api.sync.GroupId; -import org.briarproject.bramble.api.sync.InvalidMessageException; -import org.briarproject.bramble.api.sync.Message; -import org.briarproject.bramble.api.sync.MessageContext; -import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.api.sync.ValidationManager; -import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook; -import org.briarproject.bramble.api.sync.ValidationManager.MessageValidator; -import org.briarproject.bramble.util.ByteUtils; -import org.briarproject.briar.api.client.MessageQueueManager; -import org.briarproject.briar.api.client.QueueMessage; -import org.briarproject.briar.api.client.QueueMessageFactory; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; -import java.util.TreeMap; -import java.util.logging.Logger; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import javax.inject.Inject; - -import static java.util.logging.Level.INFO; -import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; -import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH; - -@Immutable -@NotNullByDefault -class MessageQueueManagerImpl implements MessageQueueManager { - - private static final String OUTGOING_POSITION_KEY = "nextOut"; - private static final String INCOMING_POSITION_KEY = "nextIn"; - private static final String PENDING_MESSAGES_KEY = "pending"; - - private static final Logger LOG = - Logger.getLogger(MessageQueueManagerImpl.class.getName()); - - private final DatabaseComponent db; - private final ClientHelper clientHelper; - private final QueueMessageFactory queueMessageFactory; - private final ValidationManager validationManager; - - @Inject - MessageQueueManagerImpl(DatabaseComponent db, ClientHelper clientHelper, - QueueMessageFactory queueMessageFactory, - ValidationManager validationManager) { - this.db = db; - this.clientHelper = clientHelper; - this.queueMessageFactory = queueMessageFactory; - this.validationManager = validationManager; - } - - @Override - public QueueMessage sendMessage(Transaction txn, Group queue, - long timestamp, byte[] body, Metadata meta) throws DbException { - QueueState queueState = loadQueueState(txn, queue.getId()); - long queuePosition = queueState.outgoingPosition; - queueState.outgoingPosition++; - if (LOG.isLoggable(INFO)) - LOG.info("Sending message with position " + queuePosition); - saveQueueState(txn, queue.getId(), queueState); - QueueMessage q = queueMessageFactory.createMessage(queue.getId(), - timestamp, queuePosition, body); - db.addLocalMessage(txn, q, meta, true); - return q; - } - - @Override - public void registerMessageValidator(ClientId c, QueueMessageValidator v) { - validationManager.registerMessageValidator(c, - new DelegatingMessageValidator(v)); - } - - @Override - public void registerIncomingMessageHook(ClientId c, - IncomingQueueMessageHook hook) { - validationManager.registerIncomingMessageHook(c, - new DelegatingIncomingMessageHook(hook)); - } - - private QueueState loadQueueState(Transaction txn, GroupId g) - throws DbException { - try { - TreeMap pending = new TreeMap<>(); - Metadata groupMeta = db.getGroupMetadata(txn, g); - byte[] raw = groupMeta.get(QUEUE_STATE_KEY); - if (raw == null) return new QueueState(0, 0, pending); - BdfDictionary d = clientHelper.toDictionary(raw, 0, raw.length); - long outgoingPosition = d.getLong(OUTGOING_POSITION_KEY); - long incomingPosition = d.getLong(INCOMING_POSITION_KEY); - BdfList pendingList = d.getList(PENDING_MESSAGES_KEY); - for (int i = 0; i < pendingList.size(); i++) { - BdfList item = pendingList.getList(i); - if (item.size() != 2) throw new FormatException(); - pending.put(item.getLong(0), new MessageId(item.getRaw(1))); - } - return new QueueState(outgoingPosition, incomingPosition, pending); - } catch (FormatException e) { - throw new DbException(e); - } - } - - private void saveQueueState(Transaction txn, GroupId g, - QueueState queueState) throws DbException { - try { - BdfDictionary d = new BdfDictionary(); - d.put(OUTGOING_POSITION_KEY, queueState.outgoingPosition); - d.put(INCOMING_POSITION_KEY, queueState.incomingPosition); - BdfList pendingList = new BdfList(); - for (Entry e : queueState.pending.entrySet()) - pendingList.add(BdfList.of(e.getKey(), e.getValue())); - d.put(PENDING_MESSAGES_KEY, pendingList); - Metadata groupMeta = new Metadata(); - groupMeta.put(QUEUE_STATE_KEY, clientHelper.toByteArray(d)); - db.mergeGroupMetadata(txn, g, groupMeta); - } catch (FormatException e) { - throw new RuntimeException(e); - } - } - - private static class QueueState { - - private long outgoingPosition, incomingPosition; - private final TreeMap pending; - - private QueueState(long outgoingPosition, long incomingPosition, - TreeMap pending) { - this.outgoingPosition = outgoingPosition; - this.incomingPosition = incomingPosition; - this.pending = pending; - } - - @Nullable - MessageId popIncomingMessageId() { - Iterator> it = pending.entrySet().iterator(); - if (!it.hasNext()) { - LOG.info("No pending messages"); - return null; - } - Entry e = it.next(); - if (!e.getKey().equals(incomingPosition)) { - if (LOG.isLoggable(INFO)) { - LOG.info("First pending message is " + e.getKey() + ", " - + " expecting " + incomingPosition); - } - return null; - } - if (LOG.isLoggable(INFO)) - LOG.info("Removing pending message " + e.getKey()); - it.remove(); - incomingPosition++; - return e.getValue(); - } - } - - @NotNullByDefault - private static class DelegatingMessageValidator - implements MessageValidator { - - private final QueueMessageValidator delegate; - - private DelegatingMessageValidator(QueueMessageValidator delegate) { - this.delegate = delegate; - } - - @Override - public MessageContext validateMessage(Message m, Group g) - throws InvalidMessageException { - byte[] raw = m.getRaw(); - if (raw.length < QUEUE_MESSAGE_HEADER_LENGTH) - throw new InvalidMessageException(); - long queuePosition = ByteUtils.readUint64(raw, - MESSAGE_HEADER_LENGTH); - if (queuePosition < 0) throw new InvalidMessageException(); - QueueMessage q = new QueueMessage(m.getId(), m.getGroupId(), - m.getTimestamp(), queuePosition, raw); - return delegate.validateMessage(q, g); - } - } - - @NotNullByDefault - private class DelegatingIncomingMessageHook implements IncomingMessageHook { - - private final IncomingQueueMessageHook delegate; - - private DelegatingIncomingMessageHook( - IncomingQueueMessageHook delegate) { - this.delegate = delegate; - } - - @Override - public boolean incomingMessage(Transaction txn, Message m, - Metadata meta) throws DbException, InvalidMessageException { - long queuePosition = ByteUtils.readUint64(m.getRaw(), - MESSAGE_HEADER_LENGTH); - QueueState queueState = loadQueueState(txn, m.getGroupId()); - if (LOG.isLoggable(INFO)) { - LOG.info("Received message with position " - + queuePosition + ", expecting " - + queueState.incomingPosition); - } - if (queuePosition < queueState.incomingPosition) { - // A message with this queue position has already been seen - LOG.warning("Deleting message with duplicate position"); - db.deleteMessage(txn, m.getId()); - db.deleteMessageMetadata(txn, m.getId()); - } else if (queuePosition > queueState.incomingPosition) { - // The message is out of order, add it to the pending list - LOG.info("Message is out of order, adding to pending list"); - queueState.pending.put(queuePosition, m.getId()); - saveQueueState(txn, m.getGroupId(), queueState); - } else { - // The message is in order - LOG.info("Message is in order, delivering"); - QueueMessage q = new QueueMessage(m.getId(), m.getGroupId(), - m.getTimestamp(), queuePosition, m.getRaw()); - queueState.incomingPosition++; - // Collect any consecutive messages - List consecutive = new ArrayList<>(); - MessageId next; - while ((next = queueState.popIncomingMessageId()) != null) - consecutive.add(next); - // Save the queue state before passing control to the delegate - saveQueueState(txn, m.getGroupId(), queueState); - // Deliver the messages to the delegate - delegate.incomingMessage(txn, q, meta); - for (MessageId id : consecutive) { - byte[] raw = db.getRawMessage(txn, id); - if (raw == null) throw new DbException(); - meta = db.getMessageMetadata(txn, id); - q = queueMessageFactory.createMessage(id, raw); - if (LOG.isLoggable(INFO)) { - LOG.info("Delivering pending message with position " - + q.getQueuePosition()); - } - delegate.incomingMessage(txn, q, meta); - } - } - // message queues are only useful for groups with two members - // so messages don't need to be shared - return false; - } - } -} diff --git a/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java deleted file mode 100644 index 480b7670b..000000000 --- a/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.briarproject.briar.client; - -import org.briarproject.bramble.api.UniqueId; -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.MessageFactory; -import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.util.ByteUtils; -import org.briarproject.briar.api.client.QueueMessage; -import org.briarproject.briar.api.client.QueueMessageFactory; - -import javax.annotation.concurrent.Immutable; -import javax.inject.Inject; - -import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH; -import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; -import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES; -import static org.briarproject.briar.api.client.QueueMessage.MAX_QUEUE_MESSAGE_BODY_LENGTH; -import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH; - -@Immutable -@NotNullByDefault -class QueueMessageFactoryImpl implements QueueMessageFactory { - - private final MessageFactory messageFactory; - - @Inject - QueueMessageFactoryImpl(MessageFactory messageFactory) { - this.messageFactory = messageFactory; - } - - @Override - public QueueMessage createMessage(GroupId groupId, long timestamp, - long queuePosition, byte[] body) { - if (body.length > MAX_QUEUE_MESSAGE_BODY_LENGTH) - throw new IllegalArgumentException(); - byte[] messageBody = new byte[INT_64_BYTES + body.length]; - ByteUtils.writeUint64(queuePosition, messageBody, 0); - System.arraycopy(body, 0, messageBody, INT_64_BYTES, body.length); - Message m = messageFactory.createMessage(groupId, timestamp, - messageBody); - return new QueueMessage(m.getId(), groupId, timestamp, queuePosition, - m.getRaw()); - } - - @Override - public QueueMessage createMessage(MessageId id, byte[] raw) { - if (raw.length < QUEUE_MESSAGE_HEADER_LENGTH) - throw new IllegalArgumentException(); - if (raw.length > MAX_MESSAGE_LENGTH) - throw new IllegalArgumentException(); - byte[] groupId = new byte[UniqueId.LENGTH]; - System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH); - long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH); - long queuePosition = ByteUtils.readUint64(raw, MESSAGE_HEADER_LENGTH); - return new QueueMessage(id, new GroupId(groupId), timestamp, - queuePosition, raw); - } -} diff --git a/briar-core/src/test/java/org/briarproject/briar/client/MessageQueueManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/client/MessageQueueManagerImplTest.java deleted file mode 100644 index 0a11b0fea..000000000 --- a/briar-core/src/test/java/org/briarproject/briar/client/MessageQueueManagerImplTest.java +++ /dev/null @@ -1,566 +0,0 @@ -package org.briarproject.briar.client; - -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.db.DatabaseComponent; -import org.briarproject.bramble.api.db.Metadata; -import org.briarproject.bramble.api.db.Transaction; -import org.briarproject.bramble.api.sync.ClientId; -import org.briarproject.bramble.api.sync.Group; -import org.briarproject.bramble.api.sync.GroupId; -import org.briarproject.bramble.api.sync.InvalidMessageException; -import org.briarproject.bramble.api.sync.Message; -import org.briarproject.bramble.api.sync.MessageContext; -import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.bramble.api.sync.ValidationManager; -import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook; -import org.briarproject.bramble.api.sync.ValidationManager.MessageValidator; -import org.briarproject.bramble.test.CaptureArgumentAction; -import org.briarproject.bramble.test.TestUtils; -import org.briarproject.bramble.util.ByteUtils; -import org.briarproject.briar.api.client.MessageQueueManager.IncomingQueueMessageHook; -import org.briarproject.briar.api.client.MessageQueueManager.QueueMessageValidator; -import org.briarproject.briar.api.client.QueueMessage; -import org.briarproject.briar.api.client.QueueMessageFactory; -import org.briarproject.briar.test.BriarTestCase; -import org.hamcrest.Description; -import org.jmock.Expectations; -import org.jmock.Mockery; -import org.jmock.api.Action; -import org.jmock.api.Invocation; -import org.junit.Test; - -import java.util.concurrent.atomic.AtomicReference; - -import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; -import static org.briarproject.bramble.test.TestUtils.getClientId; -import static org.briarproject.bramble.test.TestUtils.getGroup; -import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY; -import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.fail; - -public class MessageQueueManagerImplTest extends BriarTestCase { - - private final ClientId clientId = getClientId(); - private final Group group = getGroup(clientId); - private final GroupId groupId = group.getId(); - private final long timestamp = System.currentTimeMillis(); - - @Test - public void testSendingMessages() throws Exception { - Mockery context = new Mockery(); - DatabaseComponent db = context.mock(DatabaseComponent.class); - ClientHelper clientHelper = context.mock(ClientHelper.class); - QueueMessageFactory queueMessageFactory = - context.mock(QueueMessageFactory.class); - ValidationManager validationManager = - context.mock(ValidationManager.class); - - Transaction txn = new Transaction(null, false); - byte[] body = new byte[123]; - Metadata groupMetadata = new Metadata(); - Metadata messageMetadata = new Metadata(); - Metadata groupMetadata1 = new Metadata(); - byte[] queueState = new byte[123]; - groupMetadata1.put(QUEUE_STATE_KEY, queueState); - - context.checking(new Expectations() {{ - // First message: queue state does not exist - oneOf(db).getGroupMetadata(txn, groupId); - will(returnValue(groupMetadata)); - oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class))); - will(new EncodeQueueStateAction(1L, 0L, new BdfList())); - oneOf(db).mergeGroupMetadata(with(txn), with(groupId), - with(any(Metadata.class))); - oneOf(queueMessageFactory).createMessage(groupId, timestamp, 0L, - body); - will(new CreateMessageAction()); - oneOf(db).addLocalMessage(with(txn), with(any(QueueMessage.class)), - with(messageMetadata), with(true)); - // Second message: queue state exists - oneOf(db).getGroupMetadata(txn, groupId); - will(returnValue(groupMetadata1)); - oneOf(clientHelper).toDictionary(queueState, 0, queueState.length); - will(new DecodeQueueStateAction(1L, 0L, new BdfList())); - oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class))); - will(new EncodeQueueStateAction(2L, 0L, new BdfList())); - oneOf(db).mergeGroupMetadata(with(txn), with(groupId), - with(any(Metadata.class))); - oneOf(queueMessageFactory).createMessage(groupId, timestamp, 1L, - body); - will(new CreateMessageAction()); - oneOf(db).addLocalMessage(with(txn), with(any(QueueMessage.class)), - with(messageMetadata), with(true)); - }}); - - MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db, - clientHelper, queueMessageFactory, validationManager); - - // First message - QueueMessage q = mqm.sendMessage(txn, group, timestamp, body, - messageMetadata); - assertEquals(groupId, q.getGroupId()); - assertEquals(timestamp, q.getTimestamp()); - assertEquals(0L, q.getQueuePosition()); - assertEquals(QUEUE_MESSAGE_HEADER_LENGTH + body.length, q.getLength()); - - // Second message - QueueMessage q1 = mqm.sendMessage(txn, group, timestamp, body, - messageMetadata); - assertEquals(groupId, q1.getGroupId()); - assertEquals(timestamp, q1.getTimestamp()); - assertEquals(1L, q1.getQueuePosition()); - assertEquals(QUEUE_MESSAGE_HEADER_LENGTH + body.length, q1.getLength()); - - context.assertIsSatisfied(); - } - - @Test - public void testValidatorRejectsShortMessage() throws Exception { - Mockery context = new Mockery(); - DatabaseComponent db = context.mock(DatabaseComponent.class); - ClientHelper clientHelper = context.mock(ClientHelper.class); - QueueMessageFactory queueMessageFactory = - context.mock(QueueMessageFactory.class); - ValidationManager validationManager = - context.mock(ValidationManager.class); - - AtomicReference captured = new AtomicReference<>(); - QueueMessageValidator queueMessageValidator = - context.mock(QueueMessageValidator.class); - // The message is too short to be a valid queue message - MessageId messageId = new MessageId(TestUtils.getRandomId()); - byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH - 1]; - Message message = new Message(messageId, groupId, timestamp, raw); - - context.checking(new Expectations() {{ - oneOf(validationManager).registerMessageValidator(with(clientId), - with(any(MessageValidator.class))); - will(new CaptureArgumentAction<>(captured, - MessageValidator.class, 1)); - }}); - - MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db, - clientHelper, queueMessageFactory, validationManager); - - // Capture the delegating message validator - mqm.registerMessageValidator(clientId, queueMessageValidator); - MessageValidator delegate = captured.get(); - assertNotNull(delegate); - // The message should be invalid - try { - delegate.validateMessage(message, group); - fail(); - } catch (InvalidMessageException expected) { - // Expected - } - - context.assertIsSatisfied(); - } - - @Test - public void testValidatorRejectsNegativeQueuePosition() throws Exception { - Mockery context = new Mockery(); - DatabaseComponent db = context.mock(DatabaseComponent.class); - ClientHelper clientHelper = context.mock(ClientHelper.class); - QueueMessageFactory queueMessageFactory = - context.mock(QueueMessageFactory.class); - ValidationManager validationManager = - context.mock(ValidationManager.class); - - AtomicReference captured = new AtomicReference<>(); - QueueMessageValidator queueMessageValidator = - context.mock(QueueMessageValidator.class); - // The message has a negative queue position - MessageId messageId = new MessageId(TestUtils.getRandomId()); - byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH]; - for (int i = 0; i < 8; i++) - raw[MESSAGE_HEADER_LENGTH + i] = (byte) 0xFF; - Message message = new Message(messageId, groupId, timestamp, raw); - - context.checking(new Expectations() {{ - oneOf(validationManager).registerMessageValidator(with(clientId), - with(any(MessageValidator.class))); - will(new CaptureArgumentAction<>(captured, - MessageValidator.class, 1)); - }}); - - MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db, - clientHelper, queueMessageFactory, validationManager); - - // Capture the delegating message validator - mqm.registerMessageValidator(clientId, queueMessageValidator); - MessageValidator delegate = captured.get(); - assertNotNull(delegate); - // The message should be invalid - try { - delegate.validateMessage(message, group); - fail(); - } catch (InvalidMessageException expected) { - // Expected - } - - context.assertIsSatisfied(); - } - - @Test - public void testValidatorDelegatesValidMessage() throws Exception { - Mockery context = new Mockery(); - DatabaseComponent db = context.mock(DatabaseComponent.class); - ClientHelper clientHelper = context.mock(ClientHelper.class); - QueueMessageFactory queueMessageFactory = - context.mock(QueueMessageFactory.class); - ValidationManager validationManager = - context.mock(ValidationManager.class); - - AtomicReference captured = new AtomicReference<>(); - QueueMessageValidator queueMessageValidator = - context.mock(QueueMessageValidator.class); - Metadata metadata = new Metadata(); - MessageContext messageContext = - new MessageContext(metadata); - // The message is valid, with a queue position of zero - MessageId messageId = new MessageId(TestUtils.getRandomId()); - byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH]; - Message message = new Message(messageId, groupId, timestamp, raw); - - context.checking(new Expectations() {{ - oneOf(validationManager).registerMessageValidator(with(clientId), - with(any(MessageValidator.class))); - will(new CaptureArgumentAction<>(captured, - MessageValidator.class, 1)); - // The message should be delegated - oneOf(queueMessageValidator).validateMessage( - with(any(QueueMessage.class)), with(group)); - will(returnValue(messageContext)); - }}); - - MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db, - clientHelper, queueMessageFactory, validationManager); - - // Capture the delegating message validator - mqm.registerMessageValidator(clientId, queueMessageValidator); - MessageValidator delegate = captured.get(); - assertNotNull(delegate); - // The message should be valid and the metadata should be returned - assertSame(messageContext, delegate.validateMessage(message, group)); - assertSame(metadata, messageContext.getMetadata()); - - context.assertIsSatisfied(); - } - - @Test - public void testIncomingMessageHookDeletesDuplicateMessage() - throws Exception { - Mockery context = new Mockery(); - DatabaseComponent db = context.mock(DatabaseComponent.class); - ClientHelper clientHelper = context.mock(ClientHelper.class); - QueueMessageFactory queueMessageFactory = - context.mock(QueueMessageFactory.class); - ValidationManager validationManager = - context.mock(ValidationManager.class); - AtomicReference captured = new AtomicReference<>(); - IncomingQueueMessageHook incomingQueueMessageHook = - context.mock(IncomingQueueMessageHook.class); - - Transaction txn = new Transaction(null, false); - Metadata groupMetadata = new Metadata(); - byte[] queueState = new byte[123]; - groupMetadata.put(QUEUE_STATE_KEY, queueState); - // The message has queue position 0 - MessageId messageId = new MessageId(TestUtils.getRandomId()); - byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH]; - Message message = new Message(messageId, groupId, timestamp, raw); - - context.checking(new Expectations() {{ - oneOf(validationManager).registerIncomingMessageHook(with(clientId), - with(any(IncomingMessageHook.class))); - will(new CaptureArgumentAction<>(captured, - IncomingMessageHook.class, 1)); - oneOf(db).getGroupMetadata(txn, groupId); - will(returnValue(groupMetadata)); - // Queue position 1 is expected - oneOf(clientHelper).toDictionary(queueState, 0, queueState.length); - will(new DecodeQueueStateAction(0L, 1L, new BdfList())); - // The message and its metadata should be deleted - oneOf(db).deleteMessage(txn, messageId); - oneOf(db).deleteMessageMetadata(txn, messageId); - }}); - - MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db, - clientHelper, queueMessageFactory, validationManager); - - // Capture the delegating incoming message hook - mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook); - IncomingMessageHook delegate = captured.get(); - assertNotNull(delegate); - // Pass the message to the hook - delegate.incomingMessage(txn, message, new Metadata()); - - context.assertIsSatisfied(); - } - - @Test - public void testIncomingMessageHookAddsOutOfOrderMessageToPendingList() - throws Exception { - Mockery context = new Mockery(); - DatabaseComponent db = context.mock(DatabaseComponent.class); - ClientHelper clientHelper = context.mock(ClientHelper.class); - QueueMessageFactory queueMessageFactory = - context.mock(QueueMessageFactory.class); - ValidationManager validationManager = - context.mock(ValidationManager.class); - AtomicReference captured = new AtomicReference<>(); - IncomingQueueMessageHook incomingQueueMessageHook = - context.mock(IncomingQueueMessageHook.class); - - Transaction txn = new Transaction(null, false); - Metadata groupMetadata = new Metadata(); - byte[] queueState = new byte[123]; - groupMetadata.put(QUEUE_STATE_KEY, queueState); - // The message has queue position 1 - MessageId messageId = new MessageId(TestUtils.getRandomId()); - byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH]; - ByteUtils.writeUint64(1L, raw, MESSAGE_HEADER_LENGTH); - Message message = new Message(messageId, groupId, timestamp, raw); - BdfList pending = BdfList.of(BdfList.of(1L, messageId)); - - context.checking(new Expectations() {{ - oneOf(validationManager).registerIncomingMessageHook(with(clientId), - with(any(IncomingMessageHook.class))); - will(new CaptureArgumentAction<>(captured, - IncomingMessageHook.class, 1)); - oneOf(db).getGroupMetadata(txn, groupId); - will(returnValue(groupMetadata)); - // Queue position 0 is expected - oneOf(clientHelper).toDictionary(queueState, 0, queueState.length); - will(new DecodeQueueStateAction(0L, 0L, new BdfList())); - // The message should be added to the pending list - oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class))); - will(new EncodeQueueStateAction(0L, 0L, pending)); - oneOf(db).mergeGroupMetadata(with(txn), with(groupId), - with(any(Metadata.class))); - }}); - - MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db, - clientHelper, queueMessageFactory, validationManager); - - // Capture the delegating incoming message hook - mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook); - IncomingMessageHook delegate = captured.get(); - assertNotNull(delegate); - // Pass the message to the hook - delegate.incomingMessage(txn, message, new Metadata()); - - context.assertIsSatisfied(); - } - - @Test - public void testIncomingMessageHookDelegatesInOrderMessage() - throws Exception { - Mockery context = new Mockery(); - DatabaseComponent db = context.mock(DatabaseComponent.class); - ClientHelper clientHelper = context.mock(ClientHelper.class); - QueueMessageFactory queueMessageFactory = - context.mock(QueueMessageFactory.class); - ValidationManager validationManager = - context.mock(ValidationManager.class); - AtomicReference captured = new AtomicReference<>(); - IncomingQueueMessageHook incomingQueueMessageHook = - context.mock(IncomingQueueMessageHook.class); - - Transaction txn = new Transaction(null, false); - Metadata groupMetadata = new Metadata(); - byte[] queueState = new byte[123]; - groupMetadata.put(QUEUE_STATE_KEY, queueState); - // The message has queue position 0 - MessageId messageId = new MessageId(TestUtils.getRandomId()); - byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH]; - Message message = new Message(messageId, groupId, timestamp, raw); - Metadata messageMetadata = new Metadata(); - - context.checking(new Expectations() {{ - oneOf(validationManager).registerIncomingMessageHook(with(clientId), - with(any(IncomingMessageHook.class))); - will(new CaptureArgumentAction<>(captured, - IncomingMessageHook.class, 1)); - oneOf(db).getGroupMetadata(txn, groupId); - will(returnValue(groupMetadata)); - // Queue position 0 is expected - oneOf(clientHelper).toDictionary(queueState, 0, queueState.length); - will(new DecodeQueueStateAction(0L, 0L, new BdfList())); - // Queue position 1 should be expected next - oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class))); - will(new EncodeQueueStateAction(0L, 1L, new BdfList())); - oneOf(db).mergeGroupMetadata(with(txn), with(groupId), - with(any(Metadata.class))); - // The message should be delegated - oneOf(incomingQueueMessageHook).incomingMessage(with(txn), - with(any(QueueMessage.class)), with(messageMetadata)); - }}); - - MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db, - clientHelper, queueMessageFactory, validationManager); - - // Capture the delegating incoming message hook - mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook); - IncomingMessageHook delegate = captured.get(); - assertNotNull(delegate); - // Pass the message to the hook - delegate.incomingMessage(txn, message, messageMetadata); - - context.assertIsSatisfied(); - } - - @Test - public void testIncomingMessageHookRetrievesPendingMessage() - throws Exception { - Mockery context = new Mockery(); - DatabaseComponent db = context.mock(DatabaseComponent.class); - ClientHelper clientHelper = context.mock(ClientHelper.class); - QueueMessageFactory queueMessageFactory = - context.mock(QueueMessageFactory.class); - ValidationManager validationManager = - context.mock(ValidationManager.class); - AtomicReference captured = new AtomicReference<>(); - IncomingQueueMessageHook incomingQueueMessageHook = - context.mock(IncomingQueueMessageHook.class); - - Transaction txn = new Transaction(null, false); - Metadata groupMetadata = new Metadata(); - byte[] queueState = new byte[123]; - groupMetadata.put(QUEUE_STATE_KEY, queueState); - // The message has queue position 0 - MessageId messageId = new MessageId(TestUtils.getRandomId()); - byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH]; - Message message = new Message(messageId, groupId, timestamp, raw); - Metadata messageMetadata = new Metadata(); - // Queue position 1 is pending - MessageId messageId1 = new MessageId(TestUtils.getRandomId()); - byte[] raw1 = new byte[QUEUE_MESSAGE_HEADER_LENGTH]; - QueueMessage message1 = new QueueMessage(messageId1, groupId, - timestamp, 1L, raw1); - Metadata messageMetadata1 = new Metadata(); - BdfList pending = BdfList.of(BdfList.of(1L, messageId1)); - - context.checking(new Expectations() {{ - oneOf(validationManager).registerIncomingMessageHook(with(clientId), - with(any(IncomingMessageHook.class))); - will(new CaptureArgumentAction<>(captured, - IncomingMessageHook.class, 1)); - oneOf(db).getGroupMetadata(txn, groupId); - will(returnValue(groupMetadata)); - // Queue position 0 is expected, position 1 is pending - oneOf(clientHelper).toDictionary(queueState, 0, queueState.length); - will(new DecodeQueueStateAction(0L, 0L, pending)); - // Queue position 2 should be expected next - oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class))); - will(new EncodeQueueStateAction(0L, 2L, new BdfList())); - oneOf(db).mergeGroupMetadata(with(txn), with(groupId), - with(any(Metadata.class))); - // The new message should be delegated - oneOf(incomingQueueMessageHook).incomingMessage(with(txn), - with(any(QueueMessage.class)), with(messageMetadata)); - // The pending message should be retrieved - oneOf(db).getRawMessage(txn, messageId1); - will(returnValue(raw1)); - oneOf(db).getMessageMetadata(txn, messageId1); - will(returnValue(messageMetadata1)); - oneOf(queueMessageFactory).createMessage(messageId1, raw1); - will(returnValue(message1)); - // The pending message should be delegated - oneOf(incomingQueueMessageHook).incomingMessage(txn, message1, - messageMetadata1); - }}); - - MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db, - clientHelper, queueMessageFactory, validationManager); - - // Capture the delegating incoming message hook - mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook); - IncomingMessageHook delegate = captured.get(); - assertNotNull(delegate); - // Pass the message to the hook - delegate.incomingMessage(txn, message, messageMetadata); - - context.assertIsSatisfied(); - } - - private class EncodeQueueStateAction implements Action { - - private final long outgoingPosition, incomingPosition; - private final BdfList pending; - - private EncodeQueueStateAction(long outgoingPosition, - long incomingPosition, BdfList pending) { - this.outgoingPosition = outgoingPosition; - this.incomingPosition = incomingPosition; - this.pending = pending; - } - - @Override - public Object invoke(Invocation invocation) throws Throwable { - BdfDictionary d = (BdfDictionary) invocation.getParameter(0); - assertEquals(outgoingPosition, d.getLong("nextOut").longValue()); - assertEquals(incomingPosition, d.getLong("nextIn").longValue()); - assertEquals(pending, d.getList("pending")); - return new byte[123]; - } - - @Override - public void describeTo(Description description) { - description.appendText("encodes a queue state"); - } - } - - private class DecodeQueueStateAction implements Action { - - private final long outgoingPosition, incomingPosition; - private final BdfList pending; - - private DecodeQueueStateAction(long outgoingPosition, - long incomingPosition, BdfList pending) { - this.outgoingPosition = outgoingPosition; - this.incomingPosition = incomingPosition; - this.pending = pending; - } - - @Override - public Object invoke(Invocation invocation) throws Throwable { - BdfDictionary d = new BdfDictionary(); - d.put("nextOut", outgoingPosition); - d.put("nextIn", incomingPosition); - d.put("pending", pending); - return d; - } - - @Override - public void describeTo(Description description) { - description.appendText("decodes a queue state"); - } - } - - private class CreateMessageAction implements Action { - - @Override - public Object invoke(Invocation invocation) throws Throwable { - GroupId groupId = (GroupId) invocation.getParameter(0); - long timestamp = (Long) invocation.getParameter(1); - long queuePosition = (Long) invocation.getParameter(2); - byte[] body = (byte[]) invocation.getParameter(3); - byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH + body.length]; - MessageId id = new MessageId(TestUtils.getRandomId()); - return new QueueMessage(id, groupId, timestamp, queuePosition, raw); - } - - @Override - public void describeTo(Description description) { - description.appendText("creates a message"); - } - } - -} From 72e9a9d807c30313bcef630a8dbba498d2559a4c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 25 Apr 2018 10:12:49 -0300 Subject: [PATCH 10/21] Address first round of review comments for new IntroductionClient --- .../event/IntroductionSucceededEvent.java | 1 - .../briar/client/BdfIncomingMessageHook.java | 15 +-- .../introduction/AbstractProtocolEngine.java | 25 ---- .../IntroduceeProtocolEngine.java | 88 +++++++------ .../IntroducerProtocolEngine.java | 57 +++------ .../introduction/IntroductionConstants.java | 1 - .../introduction/IntroductionCrypto.java | 17 ++- .../introduction/IntroductionCryptoImpl.java | 117 ++++++++++-------- .../introduction/IntroductionManagerImpl.java | 85 ++++++++----- .../introduction/IntroductionValidator.java | 3 +- .../briar/introduction/MessageEncoder.java | 4 +- .../introduction/MessageEncoderImpl.java | 10 +- .../briar/introduction/MessageMetadata.java | 9 +- .../briar/introduction/MessageParser.java | 2 +- .../briar/introduction/MessageParserImpl.java | 6 +- .../IntroductionValidatorTest.java | 2 +- .../MessageEncoderParserIntegrationTest.java | 5 +- 17 files changed, 208 insertions(+), 239 deletions(-) diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionSucceededEvent.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionSucceededEvent.java index 44d166863..88998789c 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionSucceededEvent.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/event/IntroductionSucceededEvent.java @@ -8,7 +8,6 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -// TODO still needed? public class IntroductionSucceededEvent extends Event { private final Contact contact; diff --git a/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java b/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java index 17f02ca43..bf83fb6a2 100644 --- a/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java +++ b/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java @@ -57,19 +57,14 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook { public boolean incomingMessage(Transaction txn, Message m, Metadata meta) throws DbException, InvalidMessageException { try { - return incomingMessage(txn, m, meta, MESSAGE_HEADER_LENGTH); + byte[] raw = m.getRaw(); + BdfList body = clientHelper.toList(raw, MESSAGE_HEADER_LENGTH, + raw.length - MESSAGE_HEADER_LENGTH); + BdfDictionary metaDictionary = metadataParser.parse(meta); + return incomingMessage(txn, m, body, metaDictionary); } catch (FormatException e) { throw new InvalidMessageException(e); } } - private boolean incomingMessage(Transaction txn, Message m, Metadata meta, - int headerLength) throws DbException, FormatException { - byte[] raw = m.getRaw(); - BdfList body = clientHelper.toList(raw, headerLength, - raw.length - headerLength); - BdfDictionary metaDictionary = metadataParser.parse(meta); - return incomingMessage(txn, m, body, metaDictionary); - } - } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java index 20ac59307..300d3f6fb 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java @@ -13,7 +13,6 @@ 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.sync.Group; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; @@ -25,8 +24,6 @@ import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; -import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION; import static org.briarproject.briar.introduction.MessageType.ABORT; import static org.briarproject.briar.introduction.MessageType.ACCEPT; import static org.briarproject.briar.introduction.MessageType.ACTIVATE; @@ -155,28 +152,6 @@ abstract class AbstractProtocolEngine } } - void markRequestUnavailableToAnswer(Transaction txn, MessageId m) - throws DbException { - BdfDictionary meta = new BdfDictionary(); - messageEncoder.setAvailableToAnswer(meta, false); - try { - clientHelper.mergeMessageMetadata(txn, m, meta); - } catch (FormatException e) { - throw new AssertionError(e); - } - } - - Map getSessions(Transaction txn, - BdfDictionary query) throws DbException, FormatException { - return clientHelper - .getMessageMetadataAsDictionary(txn, getLocalGroup().getId(), - query); - } - - private Group getLocalGroup() { - return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); - } - boolean isInvalidDependency(@Nullable MessageId lastRemoteMessageId, @Nullable MessageId dependency) { if (dependency == null) return lastRemoteMessageId != null; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index e463dd39a..8bb5781eb 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -124,8 +124,7 @@ class IntroduceeProtocolEngine @Override public IntroduceeSession onRequestMessage(Transaction txn, - IntroduceeSession session, RequestMessage m) - throws DbException, FormatException { + IntroduceeSession session, RequestMessage m) throws DbException { switch (session.getState()) { case START: return onRemoteRequest(txn, session, m); @@ -143,8 +142,7 @@ class IntroduceeProtocolEngine @Override public IntroduceeSession onAcceptMessage(Transaction txn, - IntroduceeSession session, AcceptMessage m) - throws DbException, FormatException { + IntroduceeSession session, AcceptMessage m) throws DbException { switch (session.getState()) { case START: return onRemoteResponseInStart(txn, session, m); @@ -163,8 +161,7 @@ class IntroduceeProtocolEngine @Override public IntroduceeSession onDeclineMessage(Transaction txn, - IntroduceeSession session, DeclineMessage m) - throws DbException, FormatException { + IntroduceeSession session, DeclineMessage m) throws DbException { switch (session.getState()) { case START: return onRemoteResponseInStart(txn, session, m); @@ -183,8 +180,7 @@ class IntroduceeProtocolEngine @Override public IntroduceeSession onAuthMessage(Transaction txn, - IntroduceeSession session, AuthMessage m) - throws DbException, FormatException { + IntroduceeSession session, AuthMessage m) throws DbException { switch (session.getState()) { case AWAIT_AUTH: return onRemoteAuth(txn, session, m); @@ -202,8 +198,7 @@ class IntroduceeProtocolEngine @Override public IntroduceeSession onActivateMessage(Transaction txn, - IntroduceeSession session, ActivateMessage m) - throws DbException, FormatException { + IntroduceeSession session, ActivateMessage m) throws DbException { switch (session.getState()) { case AWAIT_ACTIVATE: return onRemoteActivate(txn, session, m); @@ -221,8 +216,7 @@ class IntroduceeProtocolEngine @Override public IntroduceeSession onAbortMessage(Transaction txn, - IntroduceeSession session, AbortMessage m) - throws DbException, FormatException { + IntroduceeSession session, AbortMessage m) throws DbException { return onRemoteAbort(txn, session, m); } @@ -232,8 +226,9 @@ class IntroduceeProtocolEngine if (isInvalidDependency(s, m.getPreviousMessageId())) return abort(txn, s); - // Mark the request visible in the UI + // Mark the request visible in the UI and available to answer markMessageVisibleInUi(txn, m.getMessageId()); + markRequestAvailableToAnswer(txn, m.getMessageId(), true); // Add SessionId to message metadata addSessionId(txn, m.getMessageId(), s.getSessionId()); @@ -243,9 +238,11 @@ class IntroduceeProtocolEngine .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); // Broadcast IntroductionRequestReceivedEvent + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); Contact c = contactManager.getContact(txn, s.getIntroducer().getId(), - identityManager.getLocalAuthor(txn).getId()); - boolean contactExists = false; // TODO + localAuthor.getId()); + boolean contactExists = contactManager + .contactExists(txn, m.getAuthor().getId(), localAuthor.getId()); IntroductionRequest request = new IntroductionRequest(s.getSessionId(), m.getMessageId(), m.getGroupId(), INTRODUCEE, m.getTimestamp(), false, @@ -260,11 +257,10 @@ class IntroduceeProtocolEngine } private IntroduceeSession onLocalAccept(Transaction txn, - IntroduceeSession s, long timestamp) throws DbException { + IntroduceeSession s, long timestamp) + throws DbException { // Mark the request message unavailable to answer - MessageId requestId = s.getLastRemoteMessageId(); - if (requestId == null) throw new IllegalStateException(); - markRequestUnavailableToAnswer(txn, requestId); + markRequestsUnavailableToAnswer(txn, s); // Create ephemeral key pair and get local transport properties KeyPair keyPair = crypto.generateKeyPair(); @@ -275,7 +271,7 @@ class IntroduceeProtocolEngine // Send a ACCEPT message long localTimestamp = - Math.max(timestamp, getLocalTimestamp(s)); + Math.max(timestamp + 1, getLocalTimestamp(s)); Message sent = sendAcceptMessage(txn, s, localTimestamp, publicKey, localTimestamp, transportProperties, true); // Track the message @@ -297,14 +293,13 @@ class IntroduceeProtocolEngine } private IntroduceeSession onLocalDecline(Transaction txn, - IntroduceeSession s, long timestamp) throws DbException { + IntroduceeSession s, long timestamp) + throws DbException { // Mark the request message unavailable to answer - MessageId requestId = s.getLastRemoteMessageId(); - if (requestId == null) throw new IllegalStateException(); - markRequestUnavailableToAnswer(txn, requestId); + markRequestsUnavailableToAnswer(txn, s); // Send a DECLINE message - long localTimestamp = Math.max(timestamp, getLocalTimestamp(s)); + long localTimestamp = Math.max(timestamp + 1, getLocalTimestamp(s)); Message sent = sendDeclineMessage(txn, s, localTimestamp, true); // Track the message messageTracker.trackOutgoingMessage(txn, sent); @@ -316,7 +311,7 @@ class IntroduceeProtocolEngine private IntroduceeSession onRemoteAccept(Transaction txn, IntroduceeSession s, AcceptMessage m) - throws DbException, FormatException { + throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) return abort(txn, s); @@ -346,7 +341,7 @@ class IntroduceeProtocolEngine if (isInvalidDependency(s, m.getPreviousMessageId())) return abort(txn, s); - // Mark the request visible in the UI + // Mark the response visible in the UI markMessageVisibleInUi(txn, m.getMessageId()); // Track the incoming message @@ -401,8 +396,6 @@ class IntroduceeProtocolEngine } catch (GeneralSecurityException e) { // TODO return abort(txn, s); - } catch (FormatException e) { - throw new AssertionError(e); } if (s.getState() != AWAIT_AUTH) throw new AssertionError(); Message sent = sendAuthMessage(txn, s, getLocalTimestamp(s), mac, @@ -411,8 +404,7 @@ class IntroduceeProtocolEngine } private IntroduceeSession onRemoteAuth(Transaction txn, - IntroduceeSession s, AuthMessage m) - throws DbException, FormatException { + IntroduceeSession s, AuthMessage m) throws DbException { // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getPreviousMessageId())) return abort(txn, s); @@ -479,9 +471,7 @@ class IntroduceeProtocolEngine IntroduceeSession s, AbortMessage m) throws DbException { // Mark the request message unavailable to answer - MessageId requestId = s.getLastRemoteMessageId(); - if (requestId == null) throw new IllegalStateException(); - markRequestUnavailableToAnswer(txn, requestId); + markRequestsUnavailableToAnswer(txn, s); // Broadcast abort event for testing txn.attach(new IntroductionAbortedEvent(s.getSessionId())); @@ -495,9 +485,7 @@ class IntroduceeProtocolEngine private IntroduceeSession abort(Transaction txn, IntroduceeSession s) throws DbException { // Mark the request message unavailable to answer - MessageId requestId = s.getLastRemoteMessageId(); - if (requestId == null) throw new IllegalStateException(); - markRequestUnavailableToAnswer(txn, requestId); + markRequestsUnavailableToAnswer(txn, s); // Send an ABORT message Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s)); @@ -537,4 +525,30 @@ class IntroduceeProtocolEngine } } + private void markRequestsUnavailableToAnswer(Transaction txn, + IntroduceeSession s) throws DbException { + BdfDictionary query = messageParser + .getRequestsAvailableToAnswerQuery(s.getSessionId()); + try { + Map results = + clientHelper.getMessageMetadataAsDictionary(txn, + s.getContactGroupId(), query); + for (MessageId m : results.keySet()) + markRequestAvailableToAnswer(txn, m, false); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + private void markRequestAvailableToAnswer(Transaction txn, MessageId m, + boolean available) throws DbException { + BdfDictionary meta = new BdfDictionary(); + messageEncoder.setAvailableToAnswer(meta, available); + try { + clientHelper.mergeMessageMetadata(txn, m, meta); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java index 044b7ccd2..98ad2fe53 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java @@ -1,11 +1,9 @@ package org.briarproject.briar.introduction; -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.data.BdfDictionary; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; @@ -23,8 +21,6 @@ import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent; import org.briarproject.briar.introduction.IntroducerSession.Introducee; -import java.util.Map; - import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; @@ -97,21 +93,19 @@ class IntroducerProtocolEngine } IntroducerSession onAbortAction(Transaction txn, IntroducerSession s) - throws DbException, FormatException { + throws DbException { return abort(txn, s); } @Override public IntroducerSession onRequestMessage(Transaction txn, - IntroducerSession s, RequestMessage m) - throws DbException, FormatException { + IntroducerSession s, RequestMessage m) throws DbException { return abort(txn, s); // Invalid in this role } @Override public IntroducerSession onAcceptMessage(Transaction txn, - IntroducerSession s, AcceptMessage m) - throws DbException, FormatException { + IntroducerSession s, AcceptMessage m) throws DbException { switch (s.getState()) { case AWAIT_RESPONSES: case AWAIT_RESPONSE_A: @@ -133,8 +127,7 @@ class IntroducerProtocolEngine @Override public IntroducerSession onDeclineMessage(Transaction txn, - IntroducerSession s, DeclineMessage m) - throws DbException, FormatException { + IntroducerSession s, DeclineMessage m) throws DbException { switch (s.getState()) { case AWAIT_RESPONSES: case AWAIT_RESPONSE_A: @@ -156,7 +149,7 @@ class IntroducerProtocolEngine @Override public IntroducerSession onAuthMessage(Transaction txn, IntroducerSession s, - AuthMessage m) throws DbException, FormatException { + AuthMessage m) throws DbException { switch (s.getState()) { case AWAIT_AUTHS: case AWAIT_AUTH_A: @@ -177,8 +170,7 @@ class IntroducerProtocolEngine @Override public IntroducerSession onActivateMessage(Transaction txn, - IntroducerSession s, ActivateMessage m) - throws DbException, FormatException { + IntroducerSession s, ActivateMessage m) throws DbException { switch (s.getState()) { case AWAIT_ACTIVATES: case AWAIT_ACTIVATE_A: @@ -199,8 +191,7 @@ class IntroducerProtocolEngine @Override public IntroducerSession onAbortMessage(Transaction txn, - IntroducerSession s, AbortMessage m) - throws DbException, FormatException { + IntroducerSession s, AbortMessage m) throws DbException { return onRemoteAbort(txn, s, m); } @@ -227,8 +218,7 @@ class IntroducerProtocolEngine } private IntroducerSession onRemoteAccept(Transaction txn, - IntroducerSession s, AcceptMessage m) - throws DbException, FormatException { + IntroducerSession s, AcceptMessage m) throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) return abort(txn, s); @@ -284,8 +274,7 @@ class IntroducerProtocolEngine } private IntroducerSession onRemoteDecline(Transaction txn, - IntroducerSession s, DeclineMessage m) - throws DbException, FormatException { + IntroducerSession s, DeclineMessage m) throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) return abort(txn, s); @@ -337,7 +326,7 @@ class IntroducerProtocolEngine private IntroducerSession onRemoteResponseInStart(Transaction txn, IntroducerSession s, AbstractIntroductionMessage m) - throws DbException, FormatException { + throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) return abort(txn, s); @@ -384,8 +373,7 @@ class IntroducerProtocolEngine } private IntroducerSession onRemoteAuth(Transaction txn, - IntroducerSession s, AuthMessage m) - throws DbException, FormatException { + IntroducerSession s, AuthMessage m) throws DbException { // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) return abort(txn, s); @@ -413,8 +401,7 @@ class IntroducerProtocolEngine } private IntroducerSession onRemoteActivate(Transaction txn, - IntroducerSession s, ActivateMessage m) - throws DbException, FormatException { + IntroducerSession s, ActivateMessage m) throws DbException { // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) return abort(txn, s); @@ -441,11 +428,7 @@ class IntroducerProtocolEngine } private IntroducerSession onRemoteAbort(Transaction txn, - IntroducerSession s, AbortMessage m) - throws DbException, FormatException { - // Mark any REQUEST messages in the session unavailable to answer - markRequestsUnavailableToAnswer(txn, s); - + IntroducerSession s, AbortMessage m) throws DbException { // Forward ABORT message Introducee i = getOtherIntroducee(s, m.getGroupId()); long timestamp = getLocalTimestamp(s, i); @@ -468,10 +451,7 @@ class IntroducerProtocolEngine } private IntroducerSession abort(Transaction txn, - IntroducerSession s) throws DbException, FormatException { - // Mark any REQUEST messages in the session unavailable to answer - markRequestsUnavailableToAnswer(txn, s); - + IntroducerSession s) throws DbException { // Broadcast abort event for testing txn.attach(new IntroductionAbortedEvent(s.getSessionId())); @@ -487,15 +467,6 @@ class IntroducerProtocolEngine s.getRequestTimestamp(), introducee1, introducee2); } - private void markRequestsUnavailableToAnswer(Transaction txn, Session s) - throws DbException, FormatException { - BdfDictionary query = messageParser - .getInvitesAvailableToAnswerQuery(s.getSessionId()); - Map results = getSessions(txn, query); - for (MessageId m : results.keySet()) - markRequestUnavailableToAnswer(txn, m); - } - private Introducee getIntroducee(IntroducerSession s, GroupId g) { if (s.getIntroducee1().groupId.equals(g)) return s.getIntroducee1(); else if (s.getIntroducee2().groupId.equals(g)) diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java index bda50f47b..af93fa955 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java @@ -12,7 +12,6 @@ interface IntroductionConstants { String MSG_KEY_LOCAL = "local"; String MSG_KEY_VISIBLE_IN_UI = "visibleInUi"; String MSG_KEY_AVAILABLE_TO_ANSWER = "availableToAnswer"; - String MSG_KEY_INVITATION_ACCEPTED = "invitationAccepted"; // Session Keys String SESSION_KEY_SESSION_ID = "sessionId"; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java index a563e3c75..3573f348a 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java @@ -1,6 +1,5 @@ package org.briarproject.briar.introduction; -import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.identity.Author; @@ -15,15 +14,13 @@ interface IntroductionCrypto { /** * Returns the {@link SessionId} based on the introducer * and the two introducees. - * - * Note: The roles of Alice and Bob can be switched. */ - SessionId getSessionId(Author introducer, Author alice, Author bob); + SessionId getSessionId(Author introducer, Author local, Author remote); /** - * Returns true if the first author is indeed alice + * Returns true if the local author is alice */ - boolean isAlice(AuthorId alice, AuthorId bob); + boolean isAlice(AuthorId local, AuthorId remote); /** * Generates an agreement key pair. @@ -49,11 +46,11 @@ interface IntroductionCrypto { SecretKey deriveMacKey(SecretKey masterKey, boolean alice); /** - * Generates a MAC that covers both introducee's ephemeral public keys and - * transport properties. + * Generates a MAC that covers both introducee's ephemeral public keys, + * transport properties, Author IDs and timestamps of the accept message. */ byte[] mac(SecretKey macKey, IntroduceeSession s, AuthorId localAuthorId, - boolean alice) throws FormatException; + boolean alice); /** * Verifies a received MAC @@ -63,7 +60,7 @@ interface IntroductionCrypto { * @throws GeneralSecurityException if the verification fails */ void verifyMac(byte[] mac, IntroduceeSession s, AuthorId localAuthorId) - throws GeneralSecurityException, FormatException; + throws GeneralSecurityException; /** * Signs a nonce derived from the macKey diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java index 9bf4e46c2..18e2cbe12 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java @@ -18,7 +18,6 @@ import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.briar.api.client.SessionId; import java.security.GeneralSecurityException; -import java.util.Arrays; import java.util.Map; import javax.annotation.concurrent.Immutable; @@ -49,14 +48,14 @@ class IntroductionCryptoImpl implements IntroductionCrypto { } @Override - public SessionId getSessionId(Author introducer, Author alice, - Author bob) { - boolean isAlice = isAlice(alice.getId(), bob.getId()); + public SessionId getSessionId(Author introducer, Author local, + Author remote) { + boolean isAlice = isAlice(local.getId(), remote.getId()); byte[] hash = crypto.hash( LABEL_SESSION_ID, introducer.getId().getBytes(), - isAlice ? alice.getId().getBytes() : bob.getId().getBytes(), - isAlice ? bob.getId().getBytes() : alice.getId().getBytes() + isAlice ? local.getId().getBytes() : remote.getId().getBytes(), + isAlice ? remote.getId().getBytes() : local.getId().getBytes() ); return new SessionId(hash); } @@ -67,9 +66,9 @@ class IntroductionCryptoImpl implements IntroductionCrypto { } @Override - public boolean isAlice(AuthorId alice, AuthorId bob) { - byte[] a = alice.getBytes(); - byte[] b = bob.getBytes(); + public boolean isAlice(AuthorId local, AuthorId remote) { + byte[] a = local.getBytes(); + byte[] b = remote.getBytes(); return Bytes.COMPARATOR.compare(new Bytes(a), new Bytes(b)) < 0; } @@ -110,7 +109,7 @@ class IntroductionCryptoImpl implements IntroductionCrypto { @Override @SuppressWarnings("ConstantConditions") public byte[] mac(SecretKey macKey, IntroduceeSession s, - AuthorId localAuthorId, boolean alice) throws FormatException { + AuthorId localAuthorId, boolean alice) { return mac(macKey, s.getIntroducer().getId(), localAuthorId, s.getRemoteAuthor().getId(), s.getAcceptTimestamp(), s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(), @@ -124,7 +123,59 @@ class IntroductionCryptoImpl implements IntroductionCrypto { byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey, Map transportProperties, Map remoteTransportProperties, - boolean alice) throws FormatException { + boolean alice) { + byte[] inputs = + getMacInputs(introducerId, localAuthorId, remoteAuthorId, + acceptTimestamp, remoteAcceptTimestamp, + ephemeralPublicKey, remoteEphemeralPublicKey, + transportProperties, remoteTransportProperties, alice); + return crypto.mac( + LABEL_AUTH_MAC, + macKey, + inputs + ); + } + + @Override + @SuppressWarnings("ConstantConditions") + public void verifyMac(byte[] mac, IntroduceeSession s, + AuthorId localAuthorId) + throws GeneralSecurityException { + boolean alice = isAlice(localAuthorId, s.getRemoteAuthor().getId()); + verifyMac(mac, new SecretKey(s.getMasterKey()), + s.getIntroducer().getId(), localAuthorId, + s.getRemoteAuthor().getId(), s.getAcceptTimestamp(), + s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(), + s.getRemotePublicKey(), s.getTransportProperties(), + s.getRemoteTransportProperties(), !alice); + } + + void verifyMac(byte[] mac, SecretKey masterKey, + AuthorId introducerId, AuthorId localAuthorId, + AuthorId remoteAuthorId, long acceptTimestamp, + long remoteAcceptTimestamp, byte[] ephemeralPublicKey, + byte[] remoteEphemeralPublicKey, + Map transportProperties, + Map remoteTransportProperties, + boolean alice) throws GeneralSecurityException { + SecretKey macKey = deriveMacKey(masterKey, alice); + byte[] inputs = + getMacInputs(introducerId, localAuthorId, remoteAuthorId, + acceptTimestamp, remoteAcceptTimestamp, + ephemeralPublicKey, remoteEphemeralPublicKey, + transportProperties, remoteTransportProperties, !alice); + if (!crypto.verifyMac(mac, LABEL_AUTH_MAC, macKey, inputs)) { + throw new GeneralSecurityException(); + } + } + + private byte[] getMacInputs(AuthorId introducerId, + AuthorId localAuthorId, AuthorId remoteAuthorId, + long acceptTimestamp, long remoteAcceptTimestamp, + byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey, + Map transportProperties, + Map remoteTransportProperties, + boolean alice) { BdfList localInfo = BdfList.of( localAuthorId, acceptTimestamp, @@ -142,43 +193,10 @@ class IntroductionCryptoImpl implements IntroductionCrypto { alice ? localInfo : remoteInfo, alice ? remoteInfo : localInfo ); - return crypto.mac( - LABEL_AUTH_MAC, - macKey, - clientHelper.toByteArray(macList) - ); - } - - @Override - @SuppressWarnings("ConstantConditions") - public void verifyMac(byte[] mac, IntroduceeSession s, - AuthorId localAuthorId) - throws GeneralSecurityException, FormatException { - boolean alice = isAlice(localAuthorId, s.getRemoteAuthor().getId()); - verifyMac(mac, new SecretKey(s.getMasterKey()), - s.getIntroducer().getId(), localAuthorId, - s.getRemoteAuthor().getId(), s.getAcceptTimestamp(), - s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(), - s.getRemotePublicKey(), s.getTransportProperties(), - s.getRemoteTransportProperties(), !alice); - } - - void verifyMac(byte[] mac, SecretKey masterKey, - AuthorId introducerId, AuthorId localAuthorId, - AuthorId remoteAuthorId, long acceptTimestamp, - long remoteAcceptTimestamp, byte[] ephemeralPublicKey, - byte[] remoteEphemeralPublicKey, - Map transportProperties, - Map remoteTransportProperties, - boolean alice) throws GeneralSecurityException, FormatException { - SecretKey macKey = deriveMacKey(masterKey, alice); - byte[] calculatedMac = - mac(macKey, introducerId, localAuthorId, remoteAuthorId, - acceptTimestamp, remoteAcceptTimestamp, - ephemeralPublicKey, remoteEphemeralPublicKey, - transportProperties, remoteTransportProperties, !alice); - if (!Arrays.equals(mac, calculatedMac)) { - throw new GeneralSecurityException(); + try { + return clientHelper.toByteArray(macList); + } catch (FormatException e) { + throw new AssertionError(); } } @@ -204,7 +222,8 @@ class IntroductionCryptoImpl implements IntroductionCrypto { void verifySignature(SecretKey macKey, byte[] publicKey, byte[] signature) throws GeneralSecurityException { byte[] nonce = getNonce(macKey); - if (!crypto.verify(LABEL_AUTH_SIGN, nonce, publicKey, signature)) { + if (!crypto.verifySignature(signature, LABEL_AUTH_SIGN, nonce, + publicKey)) { throw new GeneralSecurityException(); } } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index 7b7f9cd9b..0e4639f7f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -5,6 +5,7 @@ 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.ContactManager.ContactHook; import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfList; @@ -37,6 +38,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -59,6 +61,7 @@ class IntroductionManagerImpl extends ConversationClientImpl implements IntroductionManager, Client, ContactHook { private final ContactGroupFactory contactGroupFactory; + private final ContactManager contactManager; private final MessageParser messageParser; private final SessionEncoder sessionEncoder; private final SessionParser sessionParser; @@ -74,6 +77,7 @@ class IntroductionManagerImpl extends ConversationClientImpl MetadataParser metadataParser, MessageTracker messageTracker, ContactGroupFactory contactGroupFactory, + ContactManager contactManager, MessageParser messageParser, SessionEncoder sessionEncoder, SessionParser sessionParser, @@ -83,6 +87,7 @@ class IntroductionManagerImpl extends ConversationClientImpl IdentityManager identityManager) { super(db, clientHelper, metadataParser, messageTracker); this.contactGroupFactory = contactGroupFactory; + this.contactManager = contactManager; this.messageParser = messageParser; this.sessionEncoder = sessionEncoder; this.sessionParser = sessionParser; @@ -107,8 +112,6 @@ class IntroductionManagerImpl extends ConversationClientImpl public void addingContact(Transaction txn, Contact c) throws DbException { // Create a group to share with the contact Group g = getContactGroup(c); - // Return if we've already set things up for this contact - if (db.containsGroup(txn, g.getId())) return; // Store the group and share it with the contact db.addGroup(txn, g); db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); @@ -124,12 +127,9 @@ class IntroductionManagerImpl extends ConversationClientImpl @Override public void removingContact(Transaction txn, Contact c) throws DbException { - try { - removeSessionWithIntroducer(txn, c); - abortOrRemoveSessionWithIntroducee(txn, c); - } catch (FormatException e) { - throw new AssertionError(); - } + removeSessionWithIntroducer(txn, c); + abortOrRemoveSessionWithIntroducee(txn, c); + // Remove the contact group (all messages will be removed with it) db.removeGroup(txn, getContactGroup(c)); } @@ -185,12 +185,12 @@ class IntroductionManagerImpl extends ConversationClientImpl Message m, BdfList body) throws DbException, FormatException { ContactId introducerId = getContactId(txn, m.getGroupId()); Author introducer = db.getContact(txn, introducerId).getAuthor(); - Author alice = identityManager.getLocalAuthor(txn); - Author bob = messageParser.parseRequestMessage(m, body).getAuthor(); - if (alice.equals(bob)) throw new FormatException(); - SessionId sessionId = crypto.getSessionId(introducer, alice, bob); + Author local = identityManager.getLocalAuthor(txn); + Author remote = messageParser.parseRequestMessage(m, body).getAuthor(); + if (local.equals(remote)) throw new FormatException(); + SessionId sessionId = crypto.getSessionId(introducer, local, remote); return IntroduceeSession - .getInitial(m.getGroupId(), sessionId, introducer, bob); + .getInitial(m.getGroupId(), sessionId, introducer, remote); } private S handleMessage(Transaction txn, Message m, @@ -249,7 +249,7 @@ class IntroductionManagerImpl extends ConversationClientImpl } private void storeSession(Transaction txn, MessageId storageId, - Session session) throws DbException, FormatException { + Session session) throws DbException { BdfDictionary d; if (session.getRole() == INTRODUCER) { d = sessionEncoder @@ -260,7 +260,11 @@ class IntroductionManagerImpl extends ConversationClientImpl } else { throw new AssertionError(); } - clientHelper.mergeMessageMetadata(txn, storageId, d); + try { + clientHelper.mergeMessageMetadata(txn, storageId, d); + } catch (FormatException e) { + throw new AssertionError(); + } } @Override @@ -356,7 +360,7 @@ class IntroductionManagerImpl extends ConversationClientImpl Map results = clientHelper .getMessageMetadataAsDictionary(txn, contactGroupId, query); messages = new ArrayList<>(results.size()); - for (Map.Entry e : results.entrySet()) { + for (Entry e : results.entrySet()) { MessageId m = e.getKey(); MessageMetadata meta = messageParser.parseMetadata(e.getValue()); @@ -394,11 +398,11 @@ class IntroductionManagerImpl extends ConversationClientImpl Role role = sessionParser.getRole(bdfSession); SessionId sessionId; Author author; + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); if (role == INTRODUCER) { IntroducerSession session = sessionParser.parseIntroducerSession(bdfSession); sessionId = session.getSessionId(); - LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); if (localAuthor.equals(session.getIntroducee1().author)) { author = session.getIntroducee2().author; } else { @@ -410,8 +414,14 @@ class IntroductionManagerImpl extends ConversationClientImpl sessionId = session.getSessionId(); author = session.getRemoteAuthor(); } else throw new AssertionError(); - String message = ""; // TODO - boolean contactExists = false; // TODO + Message msg = clientHelper.getMessage(txn, m); + BdfList body = clientHelper.getMessageAsList(txn, m); + if (msg == null || body == null) throw new AssertionError(); + RequestMessage rm = messageParser.parseRequestMessage(msg, body); + String message = rm.getMessage(); + boolean contactExists = contactManager + .contactExists(txn, rm.getAuthor().getId(), + localAuthor.getId()); return new IntroductionRequest(sessionId, m, contactGroupId, role, meta.getTimestamp(), meta.isLocal(), @@ -449,12 +459,16 @@ class IntroductionManagerImpl extends ConversationClientImpl } private void removeSessionWithIntroducer(Transaction txn, - Contact introducer) throws DbException, FormatException { + Contact introducer) throws DbException { BdfDictionary query = sessionEncoder .getIntroduceeSessionsByIntroducerQuery(introducer.getAuthor()); - Map sessions = clientHelper - .getMessageMetadataAsDictionary(txn, getLocalGroup().getId(), - query); + Map sessions; + try { + sessions = clientHelper.getMessageMetadataAsDictionary(txn, + getLocalGroup().getId(), query); + } catch (FormatException e) { + throw new AssertionError(e); + } for (MessageId id : sessions.keySet()) { db.deleteMessageMetadata(txn, id); // TODO needed? db.removeMessage(txn, id); @@ -462,16 +476,23 @@ class IntroductionManagerImpl extends ConversationClientImpl } private void abortOrRemoveSessionWithIntroducee(Transaction txn, - Contact c) throws DbException, FormatException { + Contact c) throws DbException { BdfDictionary query = sessionEncoder.getIntroducerSessionsQuery(); - Map sessions = clientHelper - .getMessageMetadataAsDictionary(txn, getLocalGroup().getId(), - query); + Map sessions; + try { + sessions = clientHelper.getMessageMetadataAsDictionary(txn, + getLocalGroup().getId(), query); + } catch (FormatException e) { + throw new AssertionError(); + } LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); - for (Map.Entry session : sessions - .entrySet()) { - IntroducerSession s = - sessionParser.parseIntroducerSession(session.getValue()); + for (Entry session : sessions.entrySet()) { + IntroducerSession s; + try { + s = sessionParser.parseIntroducerSession(session.getValue()); + } catch (FormatException e) { + throw new AssertionError(); + } if (s.getIntroducee1().author.equals(c.getAuthor())) { abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(), s.getIntroducee2(), localAuthor); @@ -484,7 +505,7 @@ class IntroductionManagerImpl extends ConversationClientImpl private void abortOrRemoveSessionWithIntroducee(Transaction txn, IntroducerSession s, MessageId storageId, Introducee i, - LocalAuthor localAuthor) throws DbException, FormatException { + LocalAuthor localAuthor) throws DbException { if (db.containsContact(txn, i.author.getId(), localAuthor.getId())) { IntroducerSession session = introducerEngine.onAbortAction(txn, s); storeSession(txn, storageId, session); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java index 16d26afdf..43027b40b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java @@ -78,8 +78,7 @@ class IntroductionValidator extends BdfMessageValidator { checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH); BdfDictionary meta = messageEncoder - .encodeRequestMetadata(m.getTimestamp(), false, false, - false, false); + .encodeRequestMetadata(m.getTimestamp(), false, false, false); if (previousMessageId == null) { return new BdfMessageContext(meta); } else { diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java index d619e3d76..42eb41c0f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java @@ -18,7 +18,7 @@ import javax.annotation.Nullable; interface MessageEncoder { BdfDictionary encodeRequestMetadata(long timestamp, boolean local, - boolean read, boolean available, boolean accepted); + boolean read, boolean available); BdfDictionary encodeMetadata(MessageType type, @Nullable SessionId sessionId, long timestamp, boolean local, @@ -30,8 +30,6 @@ interface MessageEncoder { void setAvailableToAnswer(BdfDictionary meta, boolean available); - void setInvitationAccepted(BdfDictionary meta, boolean accepted); - Message encodeRequestMessage(GroupId contactGroupId, long timestamp, @Nullable MessageId previousMessageId, Author author, @Nullable String message); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java index 5bade9d86..861569ac5 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java @@ -21,7 +21,6 @@ import javax.inject.Inject; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; -import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID; @@ -49,12 +48,10 @@ class MessageEncoderImpl implements MessageEncoder { @Override public BdfDictionary encodeRequestMetadata(long timestamp, - boolean local, boolean read, boolean available, - boolean accepted) { + boolean local, boolean read, boolean available) { BdfDictionary meta = encodeMetadata(REQUEST, null, timestamp, local, read, false); meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available); - meta.put(MSG_KEY_INVITATION_ACCEPTED, accepted); return meta; } @@ -90,11 +87,6 @@ class MessageEncoderImpl implements MessageEncoder { meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available); } - @Override - public void setInvitationAccepted(BdfDictionary meta, boolean accepted) { - meta.put(MSG_KEY_INVITATION_ACCEPTED, accepted); - } - @Override public Message encodeRequestMessage(GroupId contactGroupId, long timestamp, @Nullable MessageId previousMessageId, Author author, diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageMetadata.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageMetadata.java index 9b3ea54b5..102d72bfc 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageMetadata.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageMetadata.java @@ -14,11 +14,11 @@ class MessageMetadata { @Nullable private final SessionId sessionId; private final long timestamp; - private final boolean local, read, visible, available, accepted; + private final boolean local, read, visible, available; MessageMetadata(MessageType type, @Nullable SessionId sessionId, long timestamp, boolean local, boolean read, boolean visible, - boolean available, boolean accepted) { + boolean available) { this.type = type; this.sessionId = sessionId; this.timestamp = timestamp; @@ -26,7 +26,6 @@ class MessageMetadata { this.read = read; this.visible = visible; this.available = available; - this.accepted = accepted; } MessageType getMessageType() { @@ -58,8 +57,4 @@ class MessageMetadata { return available; } - public boolean wasAccepted() { - return accepted; - } - } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParser.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParser.java index 58f9dbfab..503dd4cc6 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParser.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParser.java @@ -12,7 +12,7 @@ interface MessageParser { BdfDictionary getMessagesVisibleInUiQuery(); - BdfDictionary getInvitesAvailableToAnswerQuery(SessionId sessionId); + BdfDictionary getRequestsAvailableToAnswerQuery(SessionId sessionId); MessageMetadata parseMetadata(BdfDictionary meta) throws FormatException; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java index 263af7844..ef97282dd 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java @@ -19,7 +19,6 @@ import javax.inject.Inject; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; -import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID; @@ -43,7 +42,7 @@ class MessageParserImpl implements MessageParser { } @Override - public BdfDictionary getInvitesAvailableToAnswerQuery(SessionId sessionId) { + public BdfDictionary getRequestsAvailableToAnswerQuery(SessionId sessionId) { return BdfDictionary.of( new BdfEntry(MSG_KEY_AVAILABLE_TO_ANSWER, true), new BdfEntry(MSG_KEY_MESSAGE_TYPE, REQUEST.getValue()), @@ -64,9 +63,8 @@ class MessageParserImpl implements MessageParser { boolean read = d.getBoolean(MSG_KEY_READ); boolean visible = d.getBoolean(MSG_KEY_VISIBLE_IN_UI); boolean available = d.getBoolean(MSG_KEY_AVAILABLE_TO_ANSWER, false); - boolean accepted = d.getBoolean(MSG_KEY_INVITATION_ACCEPTED, false); return new MessageMetadata(type, sessionId, timestamp, local, read, - visible, available, accepted); + visible, available); } @Override diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java index 4629a1f73..fbebeece4 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java @@ -397,7 +397,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase { context.checking(new Expectations() {{ oneOf(messageEncoder) .encodeRequestMetadata(message.getTimestamp(), false, false, - false, false); + false); will(returnValue(meta)); }}); } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java index f8c8fae34..83d461122 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java @@ -74,8 +74,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { @Test public void testRequestMessageMetadata() throws FormatException { BdfDictionary d = messageEncoder - .encodeRequestMetadata(timestamp, true, false, false, - true); + .encodeRequestMetadata(timestamp, true, false, false); MessageMetadata meta = messageParser.parseMetadata(d); assertEquals(REQUEST, meta.getMessageType()); @@ -85,7 +84,6 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { assertFalse(meta.isRead()); assertFalse(meta.isVisibleInConversation()); assertFalse(meta.isAvailableToAnswer()); - assertTrue(meta.wasAccepted()); } @Test @@ -102,7 +100,6 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { assertTrue(meta.isRead()); assertFalse(meta.isVisibleInConversation()); assertFalse(meta.isAvailableToAnswer()); - assertFalse(meta.wasAccepted()); } @Test From 94a6137a423ea8f7afe0479065759367df503739 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 25 Apr 2018 10:52:32 -0300 Subject: [PATCH 11/21] Also validate encoded message in MessageEncoder test --- .../MessageEncoderParserIntegrationTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java index 83d461122..a4c155897 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java @@ -3,14 +3,17 @@ package org.briarproject.briar.introduction; 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.MetadataEncoder; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; +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.MessageFactory; import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.test.BriarIntegrationTestComponent; @@ -29,6 +32,7 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; +import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; import static org.briarproject.briar.introduction.MessageType.ABORT; import static org.briarproject.briar.introduction.MessageType.REQUEST; import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor; @@ -45,12 +49,18 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { @Inject MessageFactory messageFactory; @Inject + MetadataEncoder metadataEncoder; + @Inject AuthorFactory authorFactory; + @Inject + Clock clock; private final MessageEncoder messageEncoder; private final MessageParser messageParser; + private final IntroductionValidator validator; private final GroupId groupId = new GroupId(getRandomId()); + private final Group group = new Group(groupId, CLIENT_ID, getRandomId()); private final long timestamp = 42L; private final SessionId sessionId = new SessionId(getRandomId()); private final MessageId previousMsgId = new MessageId(getRandomId()); @@ -68,6 +78,8 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { messageEncoder = new MessageEncoderImpl(clientHelper, messageFactory); messageParser = new MessageParserImpl(clientHelper); + validator = new IntroductionValidator(messageEncoder, clientHelper, + metadataEncoder, clock); author = getRealAuthor(authorFactory); } @@ -107,6 +119,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { Message m = messageEncoder .encodeRequestMessage(groupId, timestamp, previousMsgId, author, text); + validator.validateMessage(m, group, clientHelper.toList(m)); RequestMessage rm = messageParser.parseRequestMessage(m, clientHelper.toList(m)); @@ -122,6 +135,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { public void testRequestMessageWithPreviousMsgNull() throws FormatException { Message m = messageEncoder .encodeRequestMessage(groupId, timestamp, null, author, text); + validator.validateMessage(m, group, clientHelper.toList(m)); RequestMessage rm = messageParser.parseRequestMessage(m, clientHelper.toList(m)); @@ -133,6 +147,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { Message m = messageEncoder .encodeRequestMessage(groupId, timestamp, previousMsgId, author, null); + validator.validateMessage(m, group, clientHelper.toList(m)); RequestMessage rm = messageParser.parseRequestMessage(m, clientHelper.toList(m)); @@ -149,6 +164,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { .encodeAcceptMessage(groupId, timestamp, previousMsgId, sessionId, ephemeralPublicKey, acceptTimestamp, transportProperties); + validator.validateMessage(m, group, clientHelper.toList(m)); AcceptMessage rm = messageParser.parseAcceptMessage(m, clientHelper.toList(m)); @@ -167,6 +183,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { Message m = messageEncoder .encodeDeclineMessage(groupId, timestamp, previousMsgId, sessionId); + validator.validateMessage(m, group, clientHelper.toList(m)); DeclineMessage rm = messageParser.parseDeclineMessage(m, clientHelper.toList(m)); @@ -182,6 +199,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { Message m = messageEncoder .encodeAuthMessage(groupId, timestamp, previousMsgId, sessionId, mac, signature); + validator.validateMessage(m, group, clientHelper.toList(m)); AuthMessage rm = messageParser.parseAuthMessage(m, clientHelper.toList(m)); @@ -199,6 +217,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { Message m = messageEncoder .encodeActivateMessage(groupId, timestamp, previousMsgId, sessionId); + validator.validateMessage(m, group, clientHelper.toList(m)); ActivateMessage rm = messageParser.parseActivateMessage(m, clientHelper.toList(m)); @@ -214,6 +233,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { Message m = messageEncoder .encodeAbortMessage(groupId, timestamp, previousMsgId, sessionId); + validator.validateMessage(m, group, clientHelper.toList(m)); AbortMessage rm = messageParser.parseAbortMessage(m, clientHelper.toList(m)); From b291fcd2cda145df3b3b03a263b62d2fa117c4b9 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 25 Apr 2018 12:05:15 -0300 Subject: [PATCH 12/21] Only allow new introductions in START state When the user attempts an introduction, instead of the introduction message input field, an explanatory text will be shown and the introduction can not be made until the last one has been finished. --- .../IntroductionMessageFragment.java | 36 +++++++++++++------ .../main/res/layout/introduction_message.xml | 14 +++++++- briar-android/src/main/res/values/strings.xml | 1 + .../api/introduction/IntroductionManager.java | 2 ++ .../introduction/IntroductionManagerImpl.java | 27 ++++++++++++-- .../IntroductionIntegrationTest.java | 21 +++++++++++ 6 files changed, 88 insertions(+), 13 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java index a052e7393..7a9652a61 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/introduction/IntroductionMessageFragment.java @@ -2,6 +2,7 @@ package org.briarproject.briar.android.introduction; import android.content.Context; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v7.app.ActionBar; import android.view.LayoutInflater; import android.view.MenuItem; @@ -32,6 +33,7 @@ import im.delight.android.identicons.IdenticonDrawable; import static android.app.Activity.RESULT_OK; import static android.view.View.GONE; +import static android.view.View.VISIBLE; import static android.widget.Toast.LENGTH_SHORT; import static java.util.logging.Level.WARNING; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; @@ -124,14 +126,15 @@ public class IntroductionMessageFragment extends BaseFragment new ContactId(contactId1)); Contact c2 = contactManager.getContact( new ContactId(contactId2)); - setUpViews(c1, c2); + boolean possible = introductionManager.canIntroduce(c1, c2); + setUpViews(c1, c2, possible); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } }); } - private void setUpViews(Contact c1, Contact c2) { + private void setUpViews(Contact c1, Contact c2, boolean possible) { introductionActivity.runOnUiThreadUnlessDestroyed(() -> { contact1 = c1; contact2 = c2; @@ -146,13 +149,22 @@ public class IntroductionMessageFragment extends BaseFragment ui.contactName1.setText(c1.getAuthor().getName()); ui.contactName2.setText(c2.getAuthor().getName()); - // set button action - ui.message.setListener(IntroductionMessageFragment.this); - - // hide progress bar and show views + // hide progress bar ui.progressBar.setVisibility(GONE); - ui.message.setSendButtonEnabled(true); - ui.message.showSoftKeyboard(); + + if (possible) { + // set button action + ui.message.setListener(IntroductionMessageFragment.this); + + // show views + ui.notPossible.setVisibility(GONE); + ui.message.setVisibility(VISIBLE); + ui.message.setSendButtonEnabled(true); + ui.message.showSoftKeyboard(); + } else { + ui.notPossible.setVisibility(VISIBLE); + ui.message.setVisibility(GONE); + } }); } @@ -174,7 +186,8 @@ public class IntroductionMessageFragment extends BaseFragment ui.message.setSendButtonEnabled(false); String msg = ui.message.getText().toString(); - msg = StringUtils.truncateUtf8(msg, MAX_REQUEST_MESSAGE_LENGTH); + if (msg.equals("")) msg = null; + else msg = StringUtils.truncateUtf8(msg, MAX_REQUEST_MESSAGE_LENGTH); makeIntroduction(contact1, contact2, msg); // don't wait for the introduction to be made before finishing activity @@ -183,7 +196,8 @@ public class IntroductionMessageFragment extends BaseFragment introductionActivity.supportFinishAfterTransition(); } - private void makeIntroduction(Contact c1, Contact c2, String msg) { + private void makeIntroduction(Contact c1, Contact c2, + @Nullable String msg) { introductionActivity.runOnDbThread(() -> { // actually make the introduction try { @@ -207,6 +221,7 @@ public class IntroductionMessageFragment extends BaseFragment private final ProgressBar progressBar; private final CircleImageView avatar1, avatar2; private final TextView contactName1, contactName2; + private final TextView notPossible; private final TextInputView message; private ViewHolder(View v) { @@ -215,6 +230,7 @@ public class IntroductionMessageFragment extends BaseFragment avatar2 = v.findViewById(R.id.avatarContact2); contactName1 = v.findViewById(R.id.nameContact1); contactName2 = v.findViewById(R.id.nameContact2); + notPossible = v.findViewById(R.id.introductionNotPossibleView); message = v.findViewById(R.id.introductionMessageView); } } diff --git a/briar-android/src/main/res/layout/introduction_message.xml b/briar-android/src/main/res/layout/introduction_message.xml index e78e1c639..0cdc34ba8 100644 --- a/briar-android/src/main/res/layout/introduction_message.xml +++ b/briar-android/src/main/res/layout/introduction_message.xml @@ -94,13 +94,25 @@ android:layout_gravity="center" tools:visibility="gone"/> + + + app:maxLines="5" + tools:visibility="visible"/> \ No newline at end of file diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index d4b40f5bb..38b04cd53 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -144,6 +144,7 @@ Introduce your contacts You can introduce your contacts to each other, so they don\'t need to meet in person to connect on Briar. Select Contact + You already have one introduction in progress with these contacts. Please allow for this to finish first. If you or your contacts are rarely online, this can take some time. Introduce Contacts Add a message (optional) Make Introduction diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java index 15936e8ad..813b039de 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java @@ -25,6 +25,8 @@ public interface IntroductionManager extends ConversationClient { */ int CLIENT_VERSION = 1; + boolean canIntroduce(Contact c1, Contact c2) throws DbException; + /** * Sends two initial introduction messages. */ diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index 0e4639f7f..bfb81a4b8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -47,6 +47,7 @@ import javax.inject.Inject; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; import static org.briarproject.briar.api.introduction.Role.INTRODUCER; +import static org.briarproject.briar.introduction.IntroducerState.START; import static org.briarproject.briar.introduction.IntroductionConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.introduction.MessageType.ABORT; import static org.briarproject.briar.introduction.MessageType.ACCEPT; @@ -267,6 +268,28 @@ class IntroductionManagerImpl extends ConversationClientImpl } } + @Override + public boolean canIntroduce(Contact c1, Contact c2) throws DbException { + Transaction txn = db.startTransaction(true); + try { + // Look up the session, if there is one + Author introducer = identityManager.getLocalAuthor(txn); + SessionId sessionId = + crypto.getSessionId(introducer, c1.getAuthor(), + c2.getAuthor()); + StoredSession ss = getSession(txn, sessionId); + if (ss == null) return true; + IntroducerSession session = + sessionParser.parseIntroducerSession(ss.bdfSession); + if (session.getState() == START) return true; + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + return false; + } + @Override public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg, long timestamp) throws DbException { @@ -398,12 +421,11 @@ class IntroductionManagerImpl extends ConversationClientImpl Role role = sessionParser.getRole(bdfSession); SessionId sessionId; Author author; - LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); if (role == INTRODUCER) { IntroducerSession session = sessionParser.parseIntroducerSession(bdfSession); sessionId = session.getSessionId(); - if (localAuthor.equals(session.getIntroducee1().author)) { + if (contactGroupId.equals(session.getIntroducee1().groupId)) { author = session.getIntroducee2().author; } else { author = session.getIntroducee1().author; @@ -419,6 +441,7 @@ class IntroductionManagerImpl extends ConversationClientImpl if (msg == null || body == null) throw new AssertionError(); RequestMessage rm = messageParser.parseRequestMessage(msg, body); String message = rm.getMessage(); + LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); boolean contactExists = contactManager .contactExists(txn, rm.getAuthor().getId(), localAuthor.getId()); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 6d13b80f1..880210039 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -23,6 +23,7 @@ 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.test.TestDatabaseModule; +import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.introduction.IntroductionMessage; @@ -446,6 +447,26 @@ public class IntroductionIntegrationTest assertFalse(listener2.aborted); } + @Test(expected = ProtocolStateException.class) + public void testDoubleIntroduction() throws Exception { + // we can make an introduction + assertTrue(introductionManager0 + .canIntroduce(contact1From0, contact2From0)); + + // make the introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, null, time); + + // no more introduction allowed while the existing one is in progress + assertFalse(introductionManager0 + .canIntroduce(contact1From0, contact2From0)); + + // try it anyway and fail + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, null, time); + } + @Test public void testIntroducerRemovedCleanup() throws Exception { addListeners(true, true); From f94db28035141e20cdbe8a8a6ff61fabb6f426e9 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 25 Apr 2018 13:30:51 -0300 Subject: [PATCH 13/21] Handle and test introductions to existing contacts --- .../IntroduceeProtocolEngine.java | 55 ++++---- .../briar/introduction/IntroduceeSession.java | 2 +- .../IntroductionIntegrationTest.java | 127 +++++++++++++++++- .../IntroductionIntegrationTestComponent.java | 1 + .../test/BriarIntegrationTestComponent.java | 3 + 5 files changed, 155 insertions(+), 33 deletions(-) diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index 8bb5781eb..447141700 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -416,26 +416,26 @@ class IntroduceeProtocolEngine } catch (GeneralSecurityException e) { return abort(txn, s); } - - try { - ContactId c = contactManager - .addContact(txn, s.getRemoteAuthor(), localAuthor.getId(), - false, false); - //noinspection ConstantConditions - transportPropertyManager.addRemoteProperties(txn, c, - s.getRemoteTransportProperties()); - } catch (ContactExistsException e) { - // TODO - } - long timestamp = Math.min(s.getAcceptTimestamp(), s.getRemoteAcceptTimestamp()); if (timestamp == -1) throw new AssertionError(); - //noinspection ConstantConditions - Map keys = keyManager - .addUnboundKeys(txn, new SecretKey(s.getMasterKey()), timestamp, - isAlice(txn, s)); + Map keys = null; + try { + ContactId c = contactManager + .addContact(txn, s.getRemoteAuthor(), localAuthor.getId(), + false, false); + if (s.getRemoteTransportProperties() == null || + s.getMasterKey() == null) throw new AssertionError(); + transportPropertyManager.addRemoteProperties(txn, c, + s.getRemoteTransportProperties()); + keys = keyManager + .addUnboundKeys(txn, new SecretKey(s.getMasterKey()), + timestamp, isAlice(txn, s)); + } catch (ContactExistsException e) { + // Ignore this and continue without adding transport properties + // or unbound transport keys. Continue with keys as null. + } Message sent = sendActivateMessage(txn, s, getLocalTimestamp(s)); @@ -449,17 +449,22 @@ class IntroduceeProtocolEngine if (isInvalidDependency(s, m.getPreviousMessageId())) return abort(txn, s); - Contact c = contactManager.getContact(txn, s.getRemoteAuthor().getId(), - identityManager.getLocalAuthor(txn).getId()); - keyManager.bindKeys(txn, c.getId(), s.getTransportKeys()); - keyManager.activateKeys(txn, s.getTransportKeys()); + // Only bind keys if contact did not exist during AUTH + if (s.getTransportKeys() != null) { + Contact c = + contactManager.getContact(txn, s.getRemoteAuthor().getId(), + identityManager.getLocalAuthor(txn).getId()); + keyManager.bindKeys(txn, c.getId(), s.getTransportKeys()); + keyManager.activateKeys(txn, s.getTransportKeys()); - // TODO remove when concept of inactive contacts is removed - contactManager.setContactActive(txn, c.getId(), true); + // TODO remove when concept of inactive contacts is removed + contactManager.setContactActive(txn, c.getId(), true); - // Broadcast IntroductionSucceededEvent - IntroductionSucceededEvent e = new IntroductionSucceededEvent(c); - txn.attach(e); + // TODO move this to AUTH step when concept of inactive contacts is removed + // Broadcast IntroductionSucceededEvent + IntroductionSucceededEvent e = new IntroductionSucceededEvent(c); + txn.attach(e); + } // Move back to START state return IntroduceeSession diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java index 1b44452d9..1aadc39ad 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java @@ -132,7 +132,7 @@ class IntroduceeSession extends Session } static IntroduceeSession awaitActivate(IntroduceeSession s, AuthMessage m, - Message sent, Map transportKeys) { + Message sent, @Nullable Map transportKeys) { return new IntroduceeSession(s.getSessionId(), AWAIT_ACTIVATE, s.getRequestTimestamp(), s.contactGroupId, sent.getId(), sent.getTimestamp(), m.getMessageId(), s.introducer, null, null, diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 880210039..9c410e238 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -20,6 +20,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportPropertyManager; 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.test.TestDatabaseModule; @@ -61,6 +62,8 @@ import static org.briarproject.briar.introduction.MessageType.ACCEPT; import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class IntroductionIntegrationTest @@ -166,11 +169,34 @@ public class IntroductionIntegrationTest sync1To0(1, true); sync0To2(1, true); + // assert that introducee2 added introducee1 + Contact contact1From2 = c2.getContactManager() + .getContact(author1.getId(), author2.getId()); + + // assert that introducee2 did add transport properties + // TODO check when notion of inactive contacts has been removed +// TransportProperties tp2 = c2.getTransportPropertyManager() +// .getRemoteProperties(contact1From2.getId(), TRANSPORT_ID); +// assertFalse(tp2.isEmpty()); + + // assert that introducee2 did add the transport keys + IntroduceeSession session2 = getIntroduceeSession(c2.getClientHelper(), + introductionManager2.getContactGroup(contact0From2).getId()); + assertNotNull(session2.getTransportKeys()); + assertFalse(session2.getTransportKeys().isEmpty()); + // sync second AUTH and its forward as well as the following ACTIVATE sync2To0(2, true); sync0To1(2, true); - // sync first ACTIVATE and its forward + // assert that introducee1 really purged the key material + IntroduceeSession session1 = getIntroduceeSession(c1.getClientHelper(), + introductionManager1.getContactGroup(contact0From1).getId()); + assertNull(session1.getMasterKey()); + assertNull(session1.getEphemeralPrivateKey()); + assertNull(session1.getTransportKeys()); + + // sync second ACTIVATE and its forward sync1To0(1, true); sync0To2(1, true); @@ -467,6 +493,71 @@ public class IntroductionIntegrationTest .makeIntroduction(contact1From0, contact2From0, null, time); } + @Test + public void testIntroductionToExistingContact() throws Exception { + // let contact1 and contact2 add each other already + addContacts1And2(); + assertNotNull(contactId2From1); + assertNotNull(contactId1From2); + + // both will still accept the introduction + addListeners(true, true); + + // make the introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, null, time); + + // sync REQUEST messages + sync0To1(1, true); + sync0To2(1, true); + + // assert that introducees get notified about the existing contact + IntroductionRequest ir1 = + getIntroductionRequest(introductionManager1, contactId0From1); + assertTrue(ir1.contactExists()); + IntroductionRequest ir2 = + getIntroductionRequest(introductionManager2, contactId0From2); + assertTrue(ir2.contactExists()); + + // sync ACCEPT messages back to introducer + sync1To0(1, true); + sync2To0(1, true); + + // sync forwarded ACCEPT messages to introducees + sync0To1(1, true); + sync0To2(1, true); + + // sync first AUTH and its forward + sync1To0(1, true); + sync0To2(1, true); + + // assert that introducee2 did not add any transport properties + TransportProperties tp2 = c2.getTransportPropertyManager() + .getRemoteProperties(contactId1From2, TRANSPORT_ID); + assertTrue(tp2.isEmpty()); + + // assert that introducee2 did not add any transport keys + IntroduceeSession session2 = getIntroduceeSession(c2.getClientHelper(), + introductionManager2.getContactGroup(contact0From2).getId()); + assertNull(session2.getTransportKeys()); + + // sync second AUTH and its forward as well as the following ACTIVATE + sync2To0(2, true); + sync0To1(2, true); + + // sync second ACTIVATE and its forward + sync1To0(1, true); + sync0To2(1, true); + + // assert that no session was aborted and no success event was broadcast + assertFalse(listener1.succeeded); + assertFalse(listener2.succeeded); + assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertFalse(listener2.aborted); + } + @Test public void testIntroducerRemovedCleanup() throws Exception { addListeners(true, true); @@ -482,8 +573,7 @@ public class IntroductionIntegrationTest assertTrue(listener1.requestReceived); // get local group for introducee1 - Group group1 = - contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); + Group group1 = getLocalGroup(); // check that we have one session state assertEquals(1, c1.getClientHelper() @@ -512,8 +602,7 @@ public class IntroductionIntegrationTest assertTrue(listener1.requestReceived); // get local group for introducer - Group group0 = - contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); + Group group0 = getLocalGroup(); // check that we have one session state assertEquals(1, c0.getClientHelper() @@ -580,8 +669,7 @@ public class IntroductionIntegrationTest m.getTransportProperties()); c0.getClientHelper() .addLocalMessage(txn, msg, new BdfDictionary(), true); - Group group0 = contactGroupFactory - .createLocalGroup(CLIENT_ID, CLIENT_VERSION); + Group group0 = getLocalGroup(); BdfDictionary query = BdfDictionary.of( new BdfEntry(SESSION_KEY_SESSION_ID, m.getSessionId()) ); @@ -850,4 +938,29 @@ public class IntroductionIntegrationTest return c0.getMessageParser().parseAcceptMessage(m, body); } + private IntroductionRequest getIntroductionRequest( + IntroductionManager manager, ContactId contactId) + throws DbException { + for (IntroductionMessage im : manager + .getIntroductionMessages(contactId)) { + if (im instanceof IntroductionRequest) { + return (IntroductionRequest) im; + } + } + throw new AssertionError("No IntroductionRequest found"); + } + + private IntroduceeSession getIntroduceeSession(ClientHelper ch, + GroupId introducerGroup) throws DbException, FormatException { + Map dicts = + ch.getMessageMetadataAsDictionary(getLocalGroup().getId()); + assertEquals(1, dicts.size()); + BdfDictionary d = dicts.values().iterator().next(); + return c0.getSessionParser().parseIntroduceeSession(introducerGroup, d); + } + + private Group getLocalGroup() { + return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); + } + } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java index a46d37cbe..b8d5dedaa 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java @@ -61,5 +61,6 @@ interface IntroductionIntegrationTestComponent MessageEncoder getMessageEncoder(); MessageParser getMessageParser(); + SessionParser getSessionParser(); } diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index a76abd057..f918b0a2d 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -9,6 +9,7 @@ import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.sync.SyncSessionFactory; +import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.client.ClientModule; import org.briarproject.bramble.contact.ContactModule; import org.briarproject.bramble.crypto.CryptoModule; @@ -145,6 +146,8 @@ public interface BriarIntegrationTestComponent { TransportPropertyManager getTransportPropertyManager(); + KeyManager getKeyManager(); + AuthorFactory getAuthorFactory(); BlogFactory getBlogFactory(); From 0a5d40868634ebb5ad0ebacf8285a3da5c94e500 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 25 Apr 2018 14:42:17 -0300 Subject: [PATCH 14/21] Add a test for when one introducee had deleted the other one --- .../IntroductionIntegrationTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 9c410e238..816957dd4 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -558,6 +558,58 @@ public class IntroductionIntegrationTest assertFalse(listener2.aborted); } + @Test + public void testIntroductionToRemovedContact() throws Exception { + // let contact1 and contact2 add each other + addContacts1And2(); + assertNotNull(contactId2From1); + assertNotNull(contactId1From2); + + // only introducee1 removes introducee2 + contactManager1.removeContact(contactId2From1); + + // both will accept the introduction + addListeners(true, true); + + // make the introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, null, time); + + // sync REQUEST messages + sync0To1(1, true); + sync0To2(1, true); + + // sync ACCEPT messages back to introducer + sync1To0(1, true); + sync2To0(1, true); + + // sync forwarded ACCEPT messages to introducees + sync0To1(1, true); + sync0To2(1, true); + + // sync first AUTH and its forward + sync1To0(1, true); + sync0To2(1, true); + + // sync second AUTH and its forward as well as the following ACTIVATE + sync2To0(2, true); + sync0To1(2, true); + + // sync second ACTIVATE and its forward + sync1To0(1, true); + sync0To2(1, true); + + // Introduction only succeeded for introducee1 + assertTrue(listener1.succeeded); + assertFalse(listener2.succeeded); + + // assert that no session was aborted + assertFalse(listener0.aborted); + assertFalse(listener1.aborted); + assertFalse(listener2.aborted); + } + @Test public void testIntroducerRemovedCleanup() throws Exception { addListeners(true, true); From 0e04044ebb2b15d99cc9d82297bc38037a3c15bb Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 26 Apr 2018 11:18:04 -0300 Subject: [PATCH 15/21] Ensure that incoming messages are expected in the current state Previously, the introducer would process and forward invalid messages by the introducees. This commit adds the necessary checks and tests. --- .../IntroducerProtocolEngine.java | 227 ++++++++++-------- .../briar/introduction/IntroducerSession.java | 26 +- .../introduction/IntroductionConstants.java | 4 +- .../introduction/IntroductionManagerImpl.java | 31 ++- .../introduction/SessionEncoderImpl.java | 8 +- .../briar/introduction/SessionParserImpl.java | 14 +- .../IntroductionIntegrationTest.java | 148 +++++++++++- .../SessionEncoderParserIntegrationTest.java | 28 +-- 8 files changed, 328 insertions(+), 158 deletions(-) diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java index 98ad2fe53..2fbe0cab8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java @@ -200,21 +200,21 @@ class IntroducerProtocolEngine @Nullable String message, long timestamp) throws DbException { // Send REQUEST messages long localTimestamp = - Math.max(timestamp, getLocalTimestamp(s, s.getIntroducee1())); - Message sent1 = sendRequestMessage(txn, s.getIntroducee1(), - localTimestamp, s.getIntroducee2().author, message + Math.max(timestamp, getLocalTimestamp(s, s.getIntroduceeA())); + Message sentA = sendRequestMessage(txn, s.getIntroduceeA(), + localTimestamp, s.getIntroduceeB().author, message ); - Message sent2 = sendRequestMessage(txn, s.getIntroducee2(), - localTimestamp, s.getIntroducee1().author, message + Message sentB = sendRequestMessage(txn, s.getIntroduceeB(), + localTimestamp, s.getIntroduceeA().author, message ); // Track the messages - messageTracker.trackOutgoingMessage(txn, sent1); - messageTracker.trackOutgoingMessage(txn, sent2); + messageTracker.trackOutgoingMessage(txn, sentA); + messageTracker.trackOutgoingMessage(txn, sentB); // Move to the AWAIT_RESPONSES state - Introducee introducee1 = new Introducee(s.getIntroducee1(), sent1); - Introducee introducee2 = new Introducee(s.getIntroducee2(), sent2); + Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA); + Introducee introduceeB = new Introducee(s.getIntroduceeB(), sentB); return new IntroducerSession(s.getSessionId(), AWAIT_RESPONSES, - localTimestamp, introducee1, introducee2); + localTimestamp, introduceeA, introduceeB); } private IntroducerSession onRemoteAccept(Transaction txn, @@ -225,6 +225,14 @@ class IntroducerProtocolEngine // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) return abort(txn, s); + // The message must be expected in the current state + boolean senderIsAlice = senderIsAlice(s, m); + if (s.getState() != AWAIT_RESPONSES) { + if (senderIsAlice && s.getState() != AWAIT_RESPONSE_A) + return abort(txn, s); + else if (!senderIsAlice && s.getState() != AWAIT_RESPONSE_B) + return abort(txn, s); + } // Mark the response visible in the UI markMessageVisibleInUi(txn, m.getMessageId()); @@ -240,27 +248,24 @@ class IntroducerProtocolEngine m.getAcceptTimestamp(), m.getTransportProperties(), false); - // Move to the next state + // Create the next state IntroducerState state = AWAIT_AUTHS; - Introducee introducee1, introducee2; - Contact c; - if (i.equals(s.getIntroducee1())) { - if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_A; - introducee1 = new Introducee(s.getIntroducee1(), sent); - introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); - c = contactManager - .getContact(txn, s.getIntroducee2().author.getId(), - identityManager.getLocalAuthor(txn).getId()); - } else if (i.equals(s.getIntroducee2())) { + Introducee introduceeA, introduceeB; + if (senderIsAlice) { if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_B; - introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); - introducee2 = new Introducee(s.getIntroducee2(), sent); - c = contactManager - .getContact(txn, s.getIntroducee1().author.getId(), - identityManager.getLocalAuthor(txn).getId()); - } else throw new AssertionError(); + introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId()); + introduceeB = new Introducee(s.getIntroduceeB(), sent); + } else { + if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_A; + introduceeA = new Introducee(s.getIntroduceeA(), sent); + introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId()); + } // Broadcast IntroductionResponseReceivedEvent + AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId(); + Contact c = contactManager.getContact(txn, + senderIsAlice ? introduceeA.author.getId() : + introduceeB.author.getId(), localAuthorId); IntroductionResponse request = new IntroductionResponse(s.getSessionId(), m.getMessageId(), m.getGroupId(), INTRODUCER, m.getTimestamp(), false, @@ -269,8 +274,14 @@ class IntroducerProtocolEngine new IntroductionResponseReceivedEvent(c.getId(), request); txn.attach(e); + // Move to the next state return new IntroducerSession(s.getSessionId(), state, - s.getRequestTimestamp(), introducee1, introducee2); + s.getRequestTimestamp(), introduceeA, introduceeB); + } + + private boolean senderIsAlice(IntroducerSession s, + AbstractIntroductionMessage m) { + return m.getGroupId().equals(s.getIntroduceeA().groupId); } private IntroducerSession onRemoteDecline(Transaction txn, @@ -281,6 +292,14 @@ class IntroducerProtocolEngine // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) return abort(txn, s); + // The message must be expected in the current state + boolean senderIsAlice = senderIsAlice(s, m); + if (s.getState() != AWAIT_RESPONSES) { + if (senderIsAlice && s.getState() != AWAIT_RESPONSE_A) + return abort(txn, s); + else if (!senderIsAlice && s.getState() != AWAIT_RESPONSE_B) + return abort(txn, s); + } // Mark the response visible in the UI markMessageVisibleInUi(txn, m.getMessageId()); @@ -293,25 +312,21 @@ class IntroducerProtocolEngine long timestamp = getLocalTimestamp(s, i); Message sent = sendDeclineMessage(txn, i, timestamp, false); - // Move to the START state - Introducee introducee1, introducee2; - AuthorId localAuthorId =identityManager.getLocalAuthor(txn).getId(); - Contact c; - if (i.equals(s.getIntroducee1())) { - introducee1 = new Introducee(s.getIntroducee1(), sent); - introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); - c = contactManager - .getContact(txn, s.getIntroducee2().author.getId(), - localAuthorId); - } else if (i.equals(s.getIntroducee2())) { - introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); - introducee2 = new Introducee(s.getIntroducee2(), sent); - c = contactManager - .getContact(txn, s.getIntroducee1().author.getId(), - localAuthorId); - } else throw new AssertionError(); + // Update introducee state + Introducee introduceeA, introduceeB; + if (senderIsAlice) { + introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId()); + introduceeB = new Introducee(s.getIntroduceeB(), sent); + } else { + introduceeA = new Introducee(s.getIntroduceeA(), sent); + introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId()); + } // Broadcast IntroductionResponseReceivedEvent + AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId(); + Contact c = contactManager.getContact(txn, + senderIsAlice ? introduceeA.author.getId() : + introduceeB.author.getId(), localAuthorId); IntroductionResponse request = new IntroductionResponse(s.getSessionId(), m.getMessageId(), m.getGroupId(), INTRODUCER, m.getTimestamp(), false, @@ -321,7 +336,7 @@ class IntroducerProtocolEngine txn.attach(e); return new IntroducerSession(s.getSessionId(), START, - s.getRequestTimestamp(), introducee1, introducee2); + s.getRequestTimestamp(), introduceeA, introduceeB); } private IntroducerSession onRemoteResponseInStart(Transaction txn, @@ -341,20 +356,20 @@ class IntroducerProtocolEngine .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); Introducee i = getIntroducee(s, m.getGroupId()); - Introducee introducee1, introducee2; + Introducee introduceeA, introduceeB; AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId(); Contact c; - if (i.equals(s.getIntroducee1())) { - introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); - introducee2 = s.getIntroducee2(); + if (i.equals(s.getIntroduceeA())) { + introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId()); + introduceeB = s.getIntroduceeB(); c = contactManager - .getContact(txn, s.getIntroducee1().author.getId(), + .getContact(txn, s.getIntroduceeA().author.getId(), localAuthorId); - } else if (i.equals(s.getIntroducee2())) { - introducee1 = s.getIntroducee1(); - introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); + } else if (i.equals(s.getIntroduceeB())) { + introduceeA = s.getIntroduceeA(); + introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId()); c = contactManager - .getContact(txn, s.getIntroducee2().author.getId(), + .getContact(txn, s.getIntroduceeB().author.getId(), localAuthorId); } else throw new AssertionError(); @@ -369,7 +384,7 @@ class IntroducerProtocolEngine txn.attach(e); return new IntroducerSession(s.getSessionId(), START, - s.getRequestTimestamp(), introducee1, introducee2); + s.getRequestTimestamp(), introduceeA, introduceeB); } private IntroducerSession onRemoteAuth(Transaction txn, @@ -377,6 +392,14 @@ class IntroducerProtocolEngine // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) return abort(txn, s); + // The message must be expected in the current state + boolean senderIsAlice = senderIsAlice(s, m); + if (s.getState() != AWAIT_AUTHS) { + if (senderIsAlice && s.getState() != AWAIT_AUTH_A) + return abort(txn, s); + else if (!senderIsAlice && s.getState() != AWAIT_AUTH_B) + return abort(txn, s); + } // Forward AUTH message Introducee i = getOtherIntroducee(s, m.getGroupId()); @@ -386,18 +409,18 @@ class IntroducerProtocolEngine // Move to the next state IntroducerState state = AWAIT_ACTIVATES; - Introducee introducee1, introducee2; - if (i.equals(s.getIntroducee1())) { - if (s.getState() == AWAIT_AUTHS) state = AWAIT_AUTH_A; - introducee1 = new Introducee(s.getIntroducee1(), sent); - introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); - } else if (i.equals(s.getIntroducee2())) { + Introducee introduceeA, introduceeB; + if (senderIsAlice) { if (s.getState() == AWAIT_AUTHS) state = AWAIT_AUTH_B; - introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); - introducee2 = new Introducee(s.getIntroducee2(), sent); - } else throw new AssertionError(); + introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId()); + introduceeB = new Introducee(s.getIntroduceeB(), sent); + } else { + if (s.getState() == AWAIT_AUTHS) state = AWAIT_AUTH_A; + introduceeA = new Introducee(s.getIntroduceeA(), sent); + introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId()); + } return new IntroducerSession(s.getSessionId(), state, - s.getRequestTimestamp(), introducee1, introducee2); + s.getRequestTimestamp(), introduceeA, introduceeB); } private IntroducerSession onRemoteActivate(Transaction txn, @@ -405,26 +428,34 @@ class IntroducerProtocolEngine // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) return abort(txn, s); + // The message must be expected in the current state + boolean senderIsAlice = senderIsAlice(s, m); + if (s.getState() != AWAIT_ACTIVATES) { + if (senderIsAlice && s.getState() != AWAIT_ACTIVATE_A) + return abort(txn, s); + else if (!senderIsAlice && s.getState() != AWAIT_ACTIVATE_B) + return abort(txn, s); + } - // Forward AUTH message + // Forward ACTIVATE message Introducee i = getOtherIntroducee(s, m.getGroupId()); long timestamp = getLocalTimestamp(s, i); Message sent = sendActivateMessage(txn, i, timestamp); // Move to the next state IntroducerState state = START; - Introducee introducee1, introducee2; - if (i.equals(s.getIntroducee1())) { - if (s.getState() == AWAIT_ACTIVATES) state = AWAIT_ACTIVATE_A; - introducee1 = new Introducee(s.getIntroducee1(), sent); - introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); - } else if (i.equals(s.getIntroducee2())) { + Introducee introduceeA, introduceeB; + if (senderIsAlice) { if (s.getState() == AWAIT_ACTIVATES) state = AWAIT_ACTIVATE_B; - introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); - introducee2 = new Introducee(s.getIntroducee2(), sent); - } else throw new AssertionError(); + introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId()); + introduceeB = new Introducee(s.getIntroduceeB(), sent); + } else { + if (s.getState() == AWAIT_ACTIVATES) state = AWAIT_ACTIVATE_A; + introduceeA = new Introducee(s.getIntroduceeA(), sent); + introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId()); + } return new IntroducerSession(s.getSessionId(), state, - s.getRequestTimestamp(), introducee1, introducee2); + s.getRequestTimestamp(), introduceeA, introduceeB); } private IntroducerSession onRemoteAbort(Transaction txn, @@ -438,16 +469,16 @@ class IntroducerProtocolEngine txn.attach(new IntroductionAbortedEvent(s.getSessionId())); // Reset the session back to initial state - Introducee introducee1, introducee2; - if (i.equals(s.getIntroducee1())) { - introducee1 = new Introducee(s.getIntroducee1(), sent); - introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); - } else if (i.equals(s.getIntroducee2())) { - introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); - introducee2 = new Introducee(s.getIntroducee2(), sent); + Introducee introduceeA, introduceeB; + if (i.equals(s.getIntroduceeA())) { + introduceeA = new Introducee(s.getIntroduceeA(), sent); + introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId()); + } else if (i.equals(s.getIntroduceeB())) { + introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId()); + introduceeB = new Introducee(s.getIntroduceeB(), sent); } else throw new AssertionError(); return new IntroducerSession(s.getSessionId(), START, - s.getRequestTimestamp(), introducee1, introducee2); + s.getRequestTimestamp(), introduceeA, introduceeB); } private IntroducerSession abort(Transaction txn, @@ -456,28 +487,28 @@ class IntroducerProtocolEngine txn.attach(new IntroductionAbortedEvent(s.getSessionId())); // Send an ABORT message to both introducees - long timestamp1 = getLocalTimestamp(s, s.getIntroducee1()); - Message sent1 = sendAbortMessage(txn, s.getIntroducee1(), timestamp1); - long timestamp2 = getLocalTimestamp(s, s.getIntroducee2()); - Message sent2 = sendAbortMessage(txn, s.getIntroducee2(), timestamp2); + long timestampA = getLocalTimestamp(s, s.getIntroduceeA()); + Message sentA = sendAbortMessage(txn, s.getIntroduceeA(), timestampA); + long timestampB = getLocalTimestamp(s, s.getIntroduceeB()); + Message sentB = sendAbortMessage(txn, s.getIntroduceeB(), timestampB); // Reset the session back to initial state - Introducee introducee1 = new Introducee(s.getIntroducee1(), sent1); - Introducee introducee2 = new Introducee(s.getIntroducee2(), sent2); + Introducee introduceeA = new Introducee(s.getIntroduceeA(), sentA); + Introducee introduceeB = new Introducee(s.getIntroduceeB(), sentB); return new IntroducerSession(s.getSessionId(), START, - s.getRequestTimestamp(), introducee1, introducee2); + s.getRequestTimestamp(), introduceeA, introduceeB); } private Introducee getIntroducee(IntroducerSession s, GroupId g) { - if (s.getIntroducee1().groupId.equals(g)) return s.getIntroducee1(); - else if (s.getIntroducee2().groupId.equals(g)) - return s.getIntroducee2(); + if (s.getIntroduceeA().groupId.equals(g)) return s.getIntroduceeA(); + else if (s.getIntroduceeB().groupId.equals(g)) + return s.getIntroduceeB(); else throw new AssertionError(); } private Introducee getOtherIntroducee(IntroducerSession s, GroupId g) { - if (s.getIntroducee1().groupId.equals(g)) return s.getIntroducee2(); - else if (s.getIntroducee2().groupId.equals(g)) - return s.getIntroducee1(); + if (s.getIntroduceeA().groupId.equals(g)) return s.getIntroduceeB(); + else if (s.getIntroduceeB().groupId.equals(g)) + return s.getIntroduceeA(); else throw new AssertionError(); } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerSession.java index 906947969..c26eb26d9 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerSession.java @@ -17,21 +17,21 @@ import static org.briarproject.briar.api.introduction.Role.INTRODUCER; @NotNullByDefault class IntroducerSession extends Session { - private final Introducee introducee1, introducee2; + private final Introducee introduceeA, introduceeB; IntroducerSession(SessionId sessionId, IntroducerState state, - long requestTimestamp, Introducee introducee1, - Introducee introducee2) { + long requestTimestamp, Introducee introduceeA, + Introducee introduceeB) { super(sessionId, state, requestTimestamp); - this.introducee1 = introducee1; - this.introducee2 = introducee2; + this.introduceeA = introduceeA; + this.introduceeB = introduceeB; } - IntroducerSession(SessionId sessionId, GroupId groupId1, Author author1, - GroupId groupId2, Author author2) { + IntroducerSession(SessionId sessionId, GroupId groupIdA, Author authorA, + GroupId groupIdB, Author authorB) { this(sessionId, IntroducerState.START, -1, - new Introducee(sessionId, groupId1, author1), - new Introducee(sessionId, groupId2, author2)); + new Introducee(sessionId, groupIdA, authorA), + new Introducee(sessionId, groupIdB, authorB)); } @Override @@ -39,12 +39,12 @@ class IntroducerSession extends Session { return INTRODUCER; } - Introducee getIntroducee1() { - return introducee1; + Introducee getIntroduceeA() { + return introduceeA; } - Introducee getIntroducee2() { - return introducee2; + Introducee getIntroduceeB() { + return introduceeB; } @Immutable diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java index af93fa955..afb1ff3e7 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java @@ -23,8 +23,8 @@ interface IntroductionConstants { String SESSION_KEY_LAST_REMOTE_MESSAGE_ID = "lastRemoteMessageId"; // Session Keys Introducer - String SESSION_KEY_INTRODUCEE_1 = "introducee1"; - String SESSION_KEY_INTRODUCEE_2 = "introducee2"; + String SESSION_KEY_INTRODUCEE_A = "introduceeA"; + String SESSION_KEY_INTRODUCEE_B = "introduceeB"; String SESSION_KEY_GROUP_ID = "groupId"; String SESSION_KEY_AUTHOR = "author"; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index bfb81a4b8..959d1d92b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -308,8 +308,15 @@ class IntroductionManagerImpl extends ConversationClientImpl // This is the first request - create a new session GroupId groupId1 = getContactGroup(c1).getId(); GroupId groupId2 = getContactGroup(c2).getId(); - session = new IntroducerSession(sessionId, groupId1, - c1.getAuthor(), groupId2, c2.getAuthor()); + boolean alice = crypto.isAlice(c1.getAuthor().getId(), + c2.getAuthor().getId()); + // use fixed deterministic roles for the introducees + session = new IntroducerSession(sessionId, + alice ? groupId1 : groupId2, + alice ? c1.getAuthor() : c2.getAuthor(), + alice ? groupId2 : groupId1, + alice ? c2.getAuthor() : c1.getAuthor() + ); storageId = createStorageId(txn); } else { // An earlier request exists, so we already have a session @@ -425,10 +432,10 @@ class IntroductionManagerImpl extends ConversationClientImpl IntroducerSession session = sessionParser.parseIntroducerSession(bdfSession); sessionId = session.getSessionId(); - if (contactGroupId.equals(session.getIntroducee1().groupId)) { - author = session.getIntroducee2().author; + if (contactGroupId.equals(session.getIntroduceeA().groupId)) { + author = session.getIntroduceeB().author; } else { - author = session.getIntroducee1().author; + author = session.getIntroduceeA().author; } } else if (role == INTRODUCEE) { IntroduceeSession session = sessionParser @@ -465,10 +472,10 @@ class IntroductionManagerImpl extends ConversationClientImpl sessionParser.parseIntroducerSession(bdfSession); sessionId = session.getSessionId(); LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); - if (localAuthor.equals(session.getIntroducee1().author)) { - author = session.getIntroducee2().author; + if (localAuthor.equals(session.getIntroduceeA().author)) { + author = session.getIntroduceeB().author; } else { - author = session.getIntroducee1().author; + author = session.getIntroduceeA().author; } } else if (role == INTRODUCEE) { IntroduceeSession session = sessionParser @@ -516,12 +523,12 @@ class IntroductionManagerImpl extends ConversationClientImpl } catch (FormatException e) { throw new AssertionError(); } - if (s.getIntroducee1().author.equals(c.getAuthor())) { + if (s.getIntroduceeA().author.equals(c.getAuthor())) { abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(), - s.getIntroducee2(), localAuthor); - } else if (s.getIntroducee2().author.equals(c.getAuthor())) { + s.getIntroduceeB(), localAuthor); + } else if (s.getIntroduceeB().author.equals(c.getAuthor())) { abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(), - s.getIntroducee1(), localAuthor); + s.getIntroduceeA(), localAuthor); } } } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java index 9cf17a2d0..2ffee61d8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java @@ -23,8 +23,8 @@ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_GROUP_ID; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_A; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_B; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; @@ -72,8 +72,8 @@ class SessionEncoderImpl implements SessionEncoder { @Override public BdfDictionary encodeIntroducerSession(IntroducerSession s) { BdfDictionary d = encodeSession(s); - d.put(SESSION_KEY_INTRODUCEE_1, encodeIntroducee(s.getIntroducee1())); - d.put(SESSION_KEY_INTRODUCEE_2, encodeIntroducee(s.getIntroducee2())); + d.put(SESSION_KEY_INTRODUCEE_A, encodeIntroducee(s.getIntroduceeA())); + d.put(SESSION_KEY_INTRODUCEE_B, encodeIntroducee(s.getIntroduceeB())); return d; } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java index f269b4973..7c5b21925 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java @@ -27,8 +27,8 @@ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_GROUP_ID; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_A; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_B; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; @@ -75,12 +75,12 @@ class SessionParserImpl implements SessionParser { SessionId sessionId = getSessionId(d); IntroducerState state = IntroducerState.fromValue(getState(d)); long requestTimestamp = d.getLong(SESSION_KEY_REQUEST_TIMESTAMP); - Introducee introducee1 = parseIntroducee(sessionId, - d.getDictionary(SESSION_KEY_INTRODUCEE_1)); - Introducee introducee2 = parseIntroducee(sessionId, - d.getDictionary(SESSION_KEY_INTRODUCEE_2)); + Introducee introduceeA = parseIntroducee(sessionId, + d.getDictionary(SESSION_KEY_INTRODUCEE_A)); + Introducee introduceeB = parseIntroducee(sessionId, + d.getDictionary(SESSION_KEY_INTRODUCEE_B)); return new IntroducerSession(sessionId, state, requestTimestamp, - introducee1, introducee2); + introduceeA, introduceeB); } private Introducee parseIntroducee(SessionId sessionId, BdfDictionary d) diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 816957dd4..1adfb9207 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -54,11 +54,12 @@ import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_A; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_B; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID; import static org.briarproject.briar.introduction.MessageType.ACCEPT; +import static org.briarproject.briar.introduction.MessageType.AUTH; import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -610,6 +611,123 @@ public class IntroductionIntegrationTest assertFalse(listener2.aborted); } + /** + * One introducee illegally sends two ACCEPT messages in a row. + * The introducer should notice this and ABORT the session. + */ + @Test + public void testDoubleAccept() throws Exception { + addListeners(true, true); + + // make the introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, null, time); + + // sync REQUEST to introducee1 + sync0To1(1, true); + + // save ACCEPT from introducee1 + AcceptMessage m = (AcceptMessage) getMessageFor(c1.getClientHelper(), + contact0From1, ACCEPT); + + // sync ACCEPT back to introducer + sync1To0(1, true); + + // fake a second ACCEPT message from introducee1 + Message msg = c1.getMessageEncoder() + .encodeAcceptMessage(m.getGroupId(), clock.currentTimeMillis(), + m.getMessageId(), m.getSessionId(), + m.getEphemeralPublicKey(), m.getAcceptTimestamp(), + m.getTransportProperties()); + c1.getClientHelper().addLocalMessage(msg, new BdfDictionary(), true); + + // sync fake ACCEPT back to introducer + sync1To0(1, true); + + assertTrue(listener0.aborted); + } + + /** + * One introducee sends an ACCEPT and then another DECLINE message. + * The introducer should notice this and ABORT the session. + */ + @Test + public void testAcceptAndDecline() throws Exception { + addListeners(true, true); + + // make the introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, null, time); + + // sync REQUEST to introducee1 + sync0To1(1, true); + + // save ACCEPT from introducee1 + AcceptMessage m = (AcceptMessage) getMessageFor(c1.getClientHelper(), + contact0From1, ACCEPT); + + // sync ACCEPT back to introducer + sync1To0(1, true); + + // fake a second DECLINE message also from introducee1 + Message msg = c1.getMessageEncoder() + .encodeDeclineMessage(m.getGroupId(), clock.currentTimeMillis(), + m.getMessageId(), m.getSessionId()); + c1.getClientHelper().addLocalMessage(msg, new BdfDictionary(), true); + + // sync fake DECLINE back to introducer + sync1To0(1, true); + + assertTrue(listener0.aborted); + } + + /** + * One introducee sends two AUTH messages. + * The introducer should notice this and ABORT the session. + */ + @Test + public void testDoubleAuth() throws Exception { + addListeners(true, true); + + // make the introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, null, time); + + // sync REQUEST messages + sync0To1(1, true); + sync0To2(1, true); + + // sync ACCEPT messages + sync1To0(1, true); + sync2To0(1, true); + + // sync forwarded ACCEPT messages to introducees + sync0To1(1, true); + sync0To2(1, true); + + // save AUTH from introducee1 + AuthMessage m = (AuthMessage) getMessageFor(c1.getClientHelper(), + contact0From1, AUTH); + + // sync first AUTH message + sync1To0(1, true); + + // fake a second AUTH message also from introducee1 + Message msg = c1.getMessageEncoder() + .encodeAuthMessage(m.getGroupId(), clock.currentTimeMillis(), + m.getMessageId(), m.getSessionId(), m.getMac(), + m.getSignature()); + c1.getClientHelper().addLocalMessage(msg, new BdfDictionary(), true); + + // sync second AUTH message + sync1To0(1, true); + + assertTrue(listener0.aborted); + } + @Test public void testIntroducerRemovedCleanup() throws Exception { addListeners(true, true); @@ -955,8 +1073,8 @@ public class IntroductionIntegrationTest private void replacePreviousLocalMessageId(Author author, BdfDictionary d, MessageId id) throws FormatException { - BdfDictionary i1 = d.getDictionary(SESSION_KEY_INTRODUCEE_1); - BdfDictionary i2 = d.getDictionary(SESSION_KEY_INTRODUCEE_2); + BdfDictionary i1 = d.getDictionary(SESSION_KEY_INTRODUCEE_A); + BdfDictionary i2 = d.getDictionary(SESSION_KEY_INTRODUCEE_B); Author a1 = clientHelper .parseAndValidateAuthor(i1.getList(SESSION_KEY_AUTHOR)); Author a2 = clientHelper @@ -964,10 +1082,10 @@ public class IntroductionIntegrationTest if (a1.equals(author)) { i1.put(SESSION_KEY_LAST_LOCAL_MESSAGE_ID, id); - d.put(SESSION_KEY_INTRODUCEE_1, i1); + d.put(SESSION_KEY_INTRODUCEE_A, i1); } else if (a2.equals(author)) { i2.put(SESSION_KEY_LAST_LOCAL_MESSAGE_ID, id); - d.put(SESSION_KEY_INTRODUCEE_2, i2); + d.put(SESSION_KEY_INTRODUCEE_B, i2); } else { throw new AssertionError(); } @@ -986,8 +1104,13 @@ public class IntroductionIntegrationTest MessageId id = map.entrySet().iterator().next().getKey(); Message m = ch.getMessage(id); BdfList body = ch.getMessageAsList(id); - //noinspection ConstantConditions - return c0.getMessageParser().parseAcceptMessage(m, body); + if (type == ACCEPT) { + //noinspection ConstantConditions + return c0.getMessageParser().parseAcceptMessage(m, body); + } else if (type == AUTH) { + //noinspection ConstantConditions + return c0.getMessageParser().parseAuthMessage(m, body); + } else throw new AssertionError("Not implemented"); } private IntroductionRequest getIntroductionRequest( @@ -1002,6 +1125,15 @@ public class IntroductionIntegrationTest throw new AssertionError("No IntroductionRequest found"); } + private IntroducerSession getIntroducerSession() + throws DbException, FormatException { + Map dicts = c0.getClientHelper() + .getMessageMetadataAsDictionary(getLocalGroup().getId()); + assertEquals(1, dicts.size()); + BdfDictionary d = dicts.values().iterator().next(); + return c0.getSessionParser().parseIntroducerSession(d); + } + private IntroduceeSession getIntroduceeSession(ClientHelper ch, GroupId introducerGroup) throws DbException, FormatException { Map dicts = diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java index 00bec26a8..9d773bac0 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java @@ -102,8 +102,8 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { assertEquals(s1.getSessionId(), s2.getSessionId()); assertEquals(AWAIT_AUTHS, s1.getState()); assertEquals(s1.getState(), s2.getState()); - assertIntroduceeEquals(s1.getIntroducee1(), s2.getIntroducee1()); - assertIntroduceeEquals(s1.getIntroducee2(), s2.getIntroducee2()); + assertIntroduceeEquals(s1.getIntroduceeA(), s2.getIntroduceeA()); + assertIntroduceeEquals(s1.getIntroduceeB(), s2.getIntroduceeB()); } @Test @@ -121,19 +121,19 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { BdfDictionary d = sessionEncoder.encodeIntroducerSession(s1); IntroducerSession s2 = sessionParser.parseIntroducerSession(d); - assertNull(s1.getIntroducee1().lastLocalMessageId); - assertEquals(s1.getIntroducee1().lastLocalMessageId, - s2.getIntroducee1().lastLocalMessageId); - assertNull(s1.getIntroducee1().lastRemoteMessageId); - assertEquals(s1.getIntroducee1().lastRemoteMessageId, - s2.getIntroducee1().lastRemoteMessageId); + assertNull(s1.getIntroduceeA().lastLocalMessageId); + assertEquals(s1.getIntroduceeA().lastLocalMessageId, + s2.getIntroduceeA().lastLocalMessageId); + assertNull(s1.getIntroduceeA().lastRemoteMessageId); + assertEquals(s1.getIntroduceeA().lastRemoteMessageId, + s2.getIntroduceeA().lastRemoteMessageId); - assertNull(s1.getIntroducee2().lastLocalMessageId); - assertEquals(s1.getIntroducee2().lastLocalMessageId, - s2.getIntroducee2().lastLocalMessageId); - assertNull(s1.getIntroducee2().lastRemoteMessageId); - assertEquals(s1.getIntroducee2().lastRemoteMessageId, - s2.getIntroducee2().lastRemoteMessageId); + assertNull(s1.getIntroduceeB().lastLocalMessageId); + assertEquals(s1.getIntroduceeB().lastLocalMessageId, + s2.getIntroduceeB().lastLocalMessageId); + assertNull(s1.getIntroduceeB().lastRemoteMessageId); + assertEquals(s1.getIntroduceeB().lastRemoteMessageId, + s2.getIntroduceeB().lastRemoteMessageId); } @Test(expected = FormatException.class) From bd5504de261429a41ad9e35a3e4d6fe64d895c89 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 26 Apr 2018 16:17:38 -0300 Subject: [PATCH 16/21] Add a MAC to the ACTIVATE message to prevent the introducer to fake them A fake ACTIVATE message would cause us to activate the transport keys before the contact has received our auth message, which would compromise forward secrecy. --- .../introduction/IntroductionConstants.java | 3 + .../introduction/AbstractProtocolEngine.java | 6 +- .../briar/introduction/ActivateMessage.java | 9 +- .../IntroduceeProtocolEngine.java | 102 +++---- .../briar/introduction/IntroduceeSession.java | 248 ++++++++++-------- .../IntroducerProtocolEngine.java | 2 +- .../introduction/IntroductionConstants.java | 15 +- .../introduction/IntroductionCrypto.java | 26 +- .../introduction/IntroductionCryptoImpl.java | 84 ++++-- .../introduction/IntroductionManagerImpl.java | 8 +- .../introduction/IntroductionValidator.java | 30 ++- .../briar/introduction/MessageEncoder.java | 3 +- .../introduction/MessageEncoderImpl.java | 49 ++-- .../briar/introduction/MessageParserImpl.java | 3 +- .../briar/introduction/Session.java | 4 +- .../introduction/SessionEncoderImpl.java | 61 +++-- .../briar/introduction/SessionParserImpl.java | 72 +++-- .../IntroductionCryptoImplTest.java | 36 ++- .../IntroductionIntegrationTest.java | 22 +- .../IntroductionValidatorTest.java | 20 +- .../MessageEncoderParserIntegrationTest.java | 73 +++--- .../SessionEncoderParserIntegrationTest.java | 155 +++++++---- 22 files changed, 614 insertions(+), 417 deletions(-) diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java index 9b454218d..87681d525 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java @@ -26,4 +26,7 @@ public interface IntroductionConstants { String LABEL_AUTH_NONCE = "org.briarproject.briar.introduction/AUTH_NONCE"; + String LABEL_ACTIVATE_MAC = + "org.briarproject.briar.introduction/ACTIVATE_MAC"; + } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java index 300d3f6fb..75af1b87c 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java @@ -110,11 +110,11 @@ abstract class AbstractProtocolEngine return m; } - Message sendActivateMessage(Transaction txn, PeerSession s, long timestamp) - throws DbException { + Message sendActivateMessage(Transaction txn, PeerSession s, long timestamp, + byte[] mac) throws DbException { Message m = messageEncoder .encodeActivateMessage(s.getContactGroupId(), timestamp, - s.getLastLocalMessageId(), s.getSessionId()); + s.getLastLocalMessageId(), s.getSessionId(), mac); sendMessage(txn, ACTIVATE, s.getSessionId(), m, false); return m; } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java index e7498dec2..5f767737d 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java @@ -12,15 +12,22 @@ import javax.annotation.concurrent.Immutable; class ActivateMessage extends AbstractIntroductionMessage { private final SessionId sessionId; + private final byte[] mac; protected ActivateMessage(MessageId messageId, GroupId groupId, - long timestamp, MessageId previousMessageId, SessionId sessionId) { + long timestamp, MessageId previousMessageId, SessionId sessionId, + byte[] mac) { super(messageId, groupId, timestamp, previousMessageId); this.sessionId = sessionId; + this.mac = mac; } public SessionId getSessionId() { return sessionId; } + public byte[] getMac() { + return mac; + } + } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index 447141700..ce7d5f76c 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -4,7 +4,6 @@ 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.crypto.KeyPair; import org.briarproject.bramble.api.crypto.SecretKey; @@ -13,7 +12,6 @@ import org.briarproject.bramble.api.db.ContactExistsException; 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.identity.LocalAuthor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -354,7 +352,7 @@ class IntroduceeProtocolEngine IntroductionResponse request = new IntroductionResponse(s.getSessionId(), m.getMessageId(), m.getGroupId(), INTRODUCEE, m.getTimestamp(), false, - false, false, false, s.getRemoteAuthor().getName(), + false, false, false, s.getRemote().author.getName(), false); IntroductionResponseReceivedEvent e = new IntroductionResponseReceivedEvent(c.getId(), request); @@ -383,16 +381,18 @@ class IntroduceeProtocolEngine private IntroduceeSession onLocalAuth(Transaction txn, IntroduceeSession s) throws DbException { - boolean alice = isAlice(txn, s); byte[] mac; byte[] signature; - SecretKey masterKey; + SecretKey masterKey, aliceMacKey, bobMacKey; try { - masterKey = crypto.deriveMasterKey(s, alice); - SecretKey macKey = crypto.deriveMacKey(masterKey, alice); + masterKey = crypto.deriveMasterKey(s); + aliceMacKey = crypto.deriveMacKey(masterKey, true); + bobMacKey = crypto.deriveMacKey(masterKey, false); + SecretKey ourMacKey = s.getLocal().alice ? aliceMacKey : bobMacKey; LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); - mac = crypto.mac(macKey, s, localAuthor.getId(), alice); - signature = crypto.sign(macKey, localAuthor.getPrivateKey()); + mac = crypto.authMac(ourMacKey, s, localAuthor.getId(), + s.getLocal().alice); + signature = crypto.sign(ourMacKey, localAuthor.getPrivateKey()); } catch (GeneralSecurityException e) { // TODO return abort(txn, s); @@ -400,7 +400,8 @@ class IntroduceeProtocolEngine if (s.getState() != AWAIT_AUTH) throw new AssertionError(); Message sent = sendAuthMessage(txn, s, getLocalTimestamp(s), mac, signature); - return IntroduceeSession.addLocalAuth(s, AWAIT_AUTH, masterKey, sent); + return IntroduceeSession.addLocalAuth(s, AWAIT_AUTH, sent, masterKey, + aliceMacKey, bobMacKey); } private IntroduceeSession onRemoteAuth(Transaction txn, @@ -411,33 +412,50 @@ class IntroduceeProtocolEngine LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); try { - crypto.verifyMac(m.getMac(), s, localAuthor.getId()); + crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId()); crypto.verifySignature(m.getSignature(), s, localAuthor.getId()); } catch (GeneralSecurityException e) { return abort(txn, s); } - long timestamp = - Math.min(s.getAcceptTimestamp(), s.getRemoteAcceptTimestamp()); + long timestamp = Math.min(s.getLocal().acceptTimestamp, + s.getRemote().acceptTimestamp); if (timestamp == -1) throw new AssertionError(); - Map keys = null; + boolean contactAdded = false; try { - ContactId c = contactManager - .addContact(txn, s.getRemoteAuthor(), localAuthor.getId(), - false, false); - if (s.getRemoteTransportProperties() == null || - s.getMasterKey() == null) throw new AssertionError(); - transportPropertyManager.addRemoteProperties(txn, c, - s.getRemoteTransportProperties()); - keys = keyManager - .addUnboundKeys(txn, new SecretKey(s.getMasterKey()), - timestamp, isAlice(txn, s)); + contactManager + .addContact(txn, s.getRemote().author, localAuthor.getId(), + false, true); + contactAdded = true; } catch (ContactExistsException e) { - // Ignore this and continue without adding transport properties - // or unbound transport keys. Continue with keys as null. + // Ignore this, because the other introducee might have deleted us. + // So we still want updated transport properties + // and new transport keys. } + Contact c = contactManager.getContact(txn, s.getRemote().author.getId(), + localAuthor.getId()); - Message sent = sendActivateMessage(txn, s, getLocalTimestamp(s)); + // bind the keys to the new (or existing) contact + //noinspection ConstantConditions + Map keys = keyManager + .addUnboundKeys(txn, new SecretKey(s.getMasterKey()), + timestamp, s.getRemote().alice); + keyManager.bindKeys(txn, c.getId(), keys); + + // add signed transport properties for the contact + //noinspection ConstantConditions + transportPropertyManager.addRemoteProperties(txn, c.getId(), + s.getRemote().transportProperties); + + // send ACTIVATE message with a MAC + byte[] mac = crypto.activateMac(s); + Message sent = sendActivateMessage(txn, s, getLocalTimestamp(s), mac); + + if (contactAdded) { + // Broadcast IntroductionSucceededEvent, because contact got added + IntroductionSucceededEvent e = new IntroductionSucceededEvent(c); + txn.attach(e); + } // Move to AWAIT_ACTIVATE state and clear key material from session return IntroduceeSession.awaitActivate(s, m, sent, keys); @@ -449,23 +467,17 @@ class IntroduceeProtocolEngine if (isInvalidDependency(s, m.getPreviousMessageId())) return abort(txn, s); - // Only bind keys if contact did not exist during AUTH - if (s.getTransportKeys() != null) { - Contact c = - contactManager.getContact(txn, s.getRemoteAuthor().getId(), - identityManager.getLocalAuthor(txn).getId()); - keyManager.bindKeys(txn, c.getId(), s.getTransportKeys()); - keyManager.activateKeys(txn, s.getTransportKeys()); - - // TODO remove when concept of inactive contacts is removed - contactManager.setContactActive(txn, c.getId(), true); - - // TODO move this to AUTH step when concept of inactive contacts is removed - // Broadcast IntroductionSucceededEvent - IntroductionSucceededEvent e = new IntroductionSucceededEvent(c); - txn.attach(e); + // Validate MAC + try { + crypto.verifyActivateMac(m.getMac(), s); + } catch (GeneralSecurityException e) { + // TODO remove transport keys? + return abort(txn, s); } + // Activate transport keys + keyManager.activateKeys(txn, s.getTransportKeys()); + // Move back to START state return IntroduceeSession .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), @@ -513,12 +525,6 @@ class IntroduceeProtocolEngine s.getRequestTimestamp()); } - private boolean isAlice(Transaction txn, IntroduceeSession s) - throws DbException { - Author localAuthor = identityManager.getLocalAuthor(txn); - return crypto.isAlice(localAuthor.getId(), s.getRemoteAuthor().getId()); - } - private void addSessionId(Transaction txn, MessageId m, SessionId sessionId) throws DbException { BdfDictionary meta = new BdfDictionary(); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java index 1aadc39ad..92b06abc0 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java @@ -27,68 +27,44 @@ class IntroduceeSession extends Session implements PeerSession { private final GroupId contactGroupId; - private final long localTimestamp, acceptTimestamp, remoteAcceptTimestamp; + private final Author introducer; + private final Local local; + private final Remote remote; @Nullable - private final MessageId lastLocalMessageId, lastRemoteMessageId; - private final Author introducer, remoteAuthor; - @Nullable - private final byte[] ephemeralPublicKey, ephemeralPrivateKey; - @Nullable - private final byte[] masterKey, remoteEphemeralPublicKey; - @Nullable - private final Map transportProperties; - @Nullable - private final Map - remoteTransportProperties; + private final byte[] masterKey; @Nullable private final Map transportKeys; IntroduceeSession(SessionId sessionId, IntroduceeState state, - long requestTimestamp, GroupId contactGroupId, - @Nullable MessageId lastLocalMessageId, long localTimestamp, - @Nullable MessageId lastRemoteMessageId, Author introducer, - @Nullable byte[] ephemeralPublicKey, - @Nullable byte[] ephemeralPrivateKey, - @Nullable Map transportProperties, - long acceptTimestamp, @Nullable byte[] masterKey, - Author remoteAuthor, - @Nullable byte[] remoteEphemeralPublicKey, @Nullable - Map remoteTransportProperties, - long remoteAcceptTimestamp, + long requestTimestamp, GroupId contactGroupId, Author introducer, + Local local, Remote remote, @Nullable byte[] masterKey, @Nullable Map transportKeys) { super(sessionId, state, requestTimestamp); this.contactGroupId = contactGroupId; - this.lastLocalMessageId = lastLocalMessageId; - this.localTimestamp = localTimestamp; - this.lastRemoteMessageId = lastRemoteMessageId; this.introducer = introducer; - this.ephemeralPublicKey = ephemeralPublicKey; - this.ephemeralPrivateKey = ephemeralPrivateKey; - this.transportProperties = transportProperties; - this.acceptTimestamp = acceptTimestamp; + this.local = local; + this.remote = remote; this.masterKey = masterKey; - this.remoteAuthor = remoteAuthor; - this.remoteEphemeralPublicKey = remoteEphemeralPublicKey; - this.remoteTransportProperties = remoteTransportProperties; - this.remoteAcceptTimestamp = remoteAcceptTimestamp; this.transportKeys = transportKeys; } static IntroduceeSession getInitial(GroupId contactGroupId, - SessionId sessionId, Author introducer, Author remoteAuthor) { - return new IntroduceeSession(sessionId, START, -1, contactGroupId, null, - -1, null, introducer, null, null, null, -1, null, remoteAuthor, - null, null, -1, null); + SessionId sessionId, Author introducer, boolean localIsAlice, + Author remoteAuthor) { + Local local = + new Local(localIsAlice, null, -1, null, 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); } static IntroduceeSession addRemoteRequest(IntroduceeSession s, IntroduceeState state, RequestMessage m) { + Remote remote = new Remote(s.remote, m.getMessageId()); return new IntroduceeSession(s.getSessionId(), state, m.getTimestamp(), - s.contactGroupId, s.lastLocalMessageId, s.localTimestamp, - m.getMessageId(), s.introducer, s.ephemeralPublicKey, - s.ephemeralPrivateKey, s.transportProperties, s.acceptTimestamp, - s.masterKey, s.remoteAuthor, s.remoteEphemeralPublicKey, - s.remoteTransportProperties, s.remoteAcceptTimestamp, + s.contactGroupId, s.introducer, s.local, remote, s.masterKey, s.transportKeys); } @@ -97,57 +73,66 @@ class IntroduceeSession extends Session byte[] ephemeralPublicKey, byte[] ephemeralPrivateKey, long acceptTimestamp, Map transportProperties) { + Local local = new Local(s.local.alice, acceptMessage.getId(), + acceptMessage.getTimestamp(), ephemeralPublicKey, + ephemeralPrivateKey, transportProperties, acceptTimestamp, + null); return new IntroduceeSession(s.getSessionId(), state, - s.getRequestTimestamp(), s.contactGroupId, - acceptMessage.getId(), acceptMessage.getTimestamp(), - s.lastRemoteMessageId, s.introducer, ephemeralPublicKey, - ephemeralPrivateKey, transportProperties, - acceptTimestamp, s.masterKey, s.remoteAuthor, - s.remoteEphemeralPublicKey, s.remoteTransportProperties, - s.remoteAcceptTimestamp, s.transportKeys); + s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, + s.remote, s.masterKey, s.transportKeys); } static IntroduceeSession addRemoteAccept(IntroduceeSession s, - IntroduceeState state, AcceptMessage acceptMessage) { + IntroduceeState state, AcceptMessage m) { + Remote remote = + new Remote(s.remote.alice, s.remote.author, m.getMessageId(), + m.getEphemeralPublicKey(), m.getTransportProperties(), + m.getAcceptTimestamp(), s.remote.macKey); return new IntroduceeSession(s.getSessionId(), state, - s.getRequestTimestamp(), s.contactGroupId, s.lastLocalMessageId, - s.localTimestamp, acceptMessage.getMessageId(), s.introducer, - s.ephemeralPublicKey, s.ephemeralPrivateKey, - s.transportProperties, s.acceptTimestamp, s.masterKey, - s.remoteAuthor, acceptMessage.getEphemeralPublicKey(), - acceptMessage.getTransportProperties(), - acceptMessage.getAcceptTimestamp(), s.transportKeys); + s.getRequestTimestamp(), s.contactGroupId, s.introducer, + s.local, remote, s.masterKey, s.transportKeys); } static IntroduceeSession addLocalAuth(IntroduceeSession s, - IntroduceeState state, SecretKey masterKey, Message m) { + 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.transportProperties, 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 IntroduceeSession(s.getSessionId(), state, - s.getRequestTimestamp(), s.contactGroupId, m.getId(), - m.getTimestamp(), s.lastRemoteMessageId, s.introducer, - s.ephemeralPublicKey, s.ephemeralPrivateKey, - s.transportProperties, s.acceptTimestamp, masterKey.getBytes(), - s.remoteAuthor, s.remoteEphemeralPublicKey, - s.remoteTransportProperties, s.remoteAcceptTimestamp, - s.transportKeys); + s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, + remote, masterKey.getBytes(), s.transportKeys); } static IntroduceeSession awaitActivate(IntroduceeSession s, AuthMessage m, Message sent, @Nullable Map transportKeys) { + Local local = new Local(s.local, sent.getId(), sent.getTimestamp()); + Remote remote = new Remote(s.remote, m.getMessageId()); return new IntroduceeSession(s.getSessionId(), AWAIT_ACTIVATE, - s.getRequestTimestamp(), s.contactGroupId, sent.getId(), - sent.getTimestamp(), m.getMessageId(), s.introducer, null, null, - null, s.acceptTimestamp, null, s.getRemoteAuthor(), null, null, - s.remoteAcceptTimestamp, transportKeys); + s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, + remote, null, transportKeys); } static IntroduceeSession clear(IntroduceeSession s, @Nullable MessageId lastLocalMessageId, long localTimestamp, @Nullable MessageId lastRemoteMessageId) { + Local local = + new Local(s.local.alice, lastLocalMessageId, localTimestamp, + null, null, null, -1, null); + Remote remote = + new Remote(s.remote.alice, s.remote.author, lastRemoteMessageId, + null, null, -1, null); return new IntroduceeSession(s.getSessionId(), START, - s.getRequestTimestamp(), s.getContactGroupId(), - lastLocalMessageId, localTimestamp, lastRemoteMessageId, - s.getIntroducer(), null, null, null, -1, null, - s.getRemoteAuthor(), null, null, -1, null); + s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, + remote, null, null); } @Override @@ -155,45 +140,38 @@ class IntroduceeSession extends Session return INTRODUCEE; } + @Override public GroupId getContactGroupId() { return contactGroupId; } + @Override public long getLocalTimestamp() { - return localTimestamp; + return local.lastMessageTimestamp; } @Nullable + @Override public MessageId getLastLocalMessageId() { - return lastLocalMessageId; + return local.lastMessageId; } @Nullable + @Override public MessageId getLastRemoteMessageId() { - return lastRemoteMessageId; + return remote.lastMessageId; } Author getIntroducer() { return introducer; } - @Nullable - byte[] getEphemeralPublicKey() { - return ephemeralPublicKey; + public Local getLocal() { + return local; } - @Nullable - byte[] getEphemeralPrivateKey() { - return ephemeralPrivateKey; - } - - @Nullable - Map getTransportProperties() { - return transportProperties; - } - - long getAcceptTimestamp() { - return acceptTimestamp; + public Remote getRemote() { + return remote; } @Nullable @@ -201,27 +179,77 @@ class IntroduceeSession extends Session return masterKey; } - Author getRemoteAuthor() { - return remoteAuthor; - } - - @Nullable - byte[] getRemotePublicKey() { - return remoteEphemeralPublicKey; - } - - @Nullable - Map getRemoteTransportProperties() { - return remoteTransportProperties; - } - - long getRemoteAcceptTimestamp() { - return remoteAcceptTimestamp; - } - @Nullable Map getTransportKeys() { return transportKeys; } + abstract static class Common { + final boolean alice; + @Nullable + final MessageId lastMessageId; + @Nullable + final byte[] ephemeralPublicKey; + @Nullable + final Map transportProperties; + final long acceptTimestamp; + @Nullable + final byte[] macKey; + + private Common(boolean alice, @Nullable MessageId lastMessageId, + @Nullable byte[] ephemeralPublicKey, @Nullable + Map transportProperties, + long acceptTimestamp, @Nullable byte[] macKey) { + this.alice = alice; + this.lastMessageId = lastMessageId; + this.ephemeralPublicKey = ephemeralPublicKey; + this.transportProperties = transportProperties; + 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, @Nullable + Map transportProperties, + long acceptTimestamp, @Nullable byte[] macKey) { + super(alice, lastMessageId, ephemeralPublicKey, transportProperties, + acceptTimestamp, macKey); + this.lastMessageTimestamp = lastMessageTimestamp; + this.ephemeralPrivateKey = ephemeralPrivateKey; + } + + private Local(Local s, @Nullable MessageId lastMessageId, + long lastMessageTimestamp) { + this(s.alice, lastMessageId, lastMessageTimestamp, + s.ephemeralPublicKey, s.ephemeralPrivateKey, + s.transportProperties, s.acceptTimestamp, s.macKey); + } + } + + static class Remote extends Common { + final Author author; + + Remote(boolean alice, Author author, + @Nullable MessageId lastMessageId, + @Nullable byte[] ephemeralPublicKey, @Nullable + Map transportProperties, + long acceptTimestamp, @Nullable byte[] macKey) { + super(alice, lastMessageId, ephemeralPublicKey, transportProperties, + acceptTimestamp, macKey); + this.author = author; + } + + private Remote(Remote s, @Nullable MessageId lastMessageId) { + this(s.alice, s.author, lastMessageId, s.ephemeralPublicKey, + s.transportProperties, s.acceptTimestamp, s.macKey); + } + } + } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java index 2fbe0cab8..7115ec089 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java @@ -440,7 +440,7 @@ class IntroducerProtocolEngine // Forward ACTIVATE message Introducee i = getOtherIntroducee(s, m.getGroupId()); long timestamp = getLocalTimestamp(s, i); - Message sent = sendActivateMessage(txn, i, timestamp); + Message sent = sendActivateMessage(txn, i, timestamp, m.getMac()); // Move to the next state IntroducerState state = START; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java index afb1ff3e7..522759a55 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java @@ -30,16 +30,19 @@ interface IntroductionConstants { // 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_MASTER_KEY = "masterKey"; + String SESSION_KEY_MAC_KEY = "macKey"; + String SESSION_KEY_REMOTE_AUTHOR = "remoteAuthor"; - String SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY = "remoteEphemeralPublicKey"; - String SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES = - "remoteTransportProperties"; - String SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP = "remoteAcceptTimestamp"; - String SESSION_KEY_TRANSPORT_KEYS = "transportKeys"; } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java index 3573f348a..7504270d9 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java @@ -30,16 +30,15 @@ interface IntroductionCrypto { /** * Derives a session master key for Alice or Bob. * - * @param alice true if the session owner is Alice * @return The secret master key */ - SecretKey deriveMasterKey(IntroduceeSession s, boolean alice) + SecretKey deriveMasterKey(IntroduceeSession s) throws GeneralSecurityException; /** * Derives a MAC key from the session's master key for Alice or Bob. * - * @param masterKey The key returned by {@link #deriveMasterKey(IntroduceeSession, boolean)} + * @param masterKey The key returned by {@link #deriveMasterKey(IntroduceeSession)} * @param alice true for Alice's MAC key, false for Bob's * @return The MAC key */ @@ -49,17 +48,17 @@ interface IntroductionCrypto { * Generates a MAC that covers both introducee's ephemeral public keys, * transport properties, Author IDs and timestamps of the accept message. */ - byte[] mac(SecretKey macKey, IntroduceeSession s, AuthorId localAuthorId, - boolean alice); + byte[] authMac(SecretKey macKey, IntroduceeSession s, + AuthorId localAuthorId, boolean alice); /** * Verifies a received MAC * * @param mac The MAC to verify - * as returned by {@link #deriveMasterKey(IntroduceeSession, boolean)} + * as returned by {@link #deriveMasterKey(IntroduceeSession)} * @throws GeneralSecurityException if the verification fails */ - void verifyMac(byte[] mac, IntroduceeSession s, AuthorId localAuthorId) + void verifyAuthMac(byte[] mac, IntroduceeSession s, AuthorId localAuthorId) throws GeneralSecurityException; /** @@ -82,4 +81,17 @@ interface IntroductionCrypto { void verifySignature(byte[] signature, IntroduceeSession s, AuthorId localAuthorId) throws GeneralSecurityException; + /** + * Generates a MAC using the local MAC key. + */ + byte[] activateMac(IntroduceeSession s); + + /** + * Verifies a MAC from an ACTIVATE message. + * + * @throws GeneralSecurityException if the verification fails + */ + void verifyActivateMac(byte[] mac, IntroduceeSession s) + throws GeneralSecurityException; + } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java index 18e2cbe12..6712a3153 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java @@ -23,6 +23,7 @@ import java.util.Map; 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; @@ -74,10 +75,14 @@ class IntroductionCryptoImpl implements IntroductionCrypto { @Override @SuppressWarnings("ConstantConditions") - public SecretKey deriveMasterKey(IntroduceeSession s, boolean alice) + public SecretKey deriveMasterKey(IntroduceeSession s) throws GeneralSecurityException { - return deriveMasterKey(s.getEphemeralPublicKey(), - s.getEphemeralPrivateKey(), s.getRemotePublicKey(), alice); + return deriveMasterKey( + s.getLocal().ephemeralPublicKey, + s.getLocal().ephemeralPrivateKey, + s.getRemote().ephemeralPublicKey, + s.getLocal().alice + ); } SecretKey deriveMasterKey(byte[] publicKey, byte[] privateKey, @@ -108,16 +113,17 @@ class IntroductionCryptoImpl implements IntroductionCrypto { @Override @SuppressWarnings("ConstantConditions") - public byte[] mac(SecretKey macKey, IntroduceeSession s, + public byte[] authMac(SecretKey macKey, IntroduceeSession s, AuthorId localAuthorId, boolean alice) { - return mac(macKey, s.getIntroducer().getId(), localAuthorId, - s.getRemoteAuthor().getId(), s.getAcceptTimestamp(), - s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(), - s.getRemotePublicKey(), s.getTransportProperties(), - s.getRemoteTransportProperties(), alice); + return authMac(macKey, s.getIntroducer().getId(), localAuthorId, + s.getRemote().author.getId(), s.getLocal().acceptTimestamp, + s.getRemote().acceptTimestamp, s.getLocal().ephemeralPublicKey, + s.getRemote().ephemeralPublicKey, + s.getLocal().transportProperties, + s.getRemote().transportProperties, alice); } - byte[] mac(SecretKey macKey, AuthorId introducerId, + byte[] authMac(SecretKey macKey, AuthorId introducerId, AuthorId localAuthorId, AuthorId remoteAuthorId, long acceptTimestamp, long remoteAcceptTimestamp, byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey, @@ -125,7 +131,7 @@ class IntroductionCryptoImpl implements IntroductionCrypto { Map remoteTransportProperties, boolean alice) { byte[] inputs = - getMacInputs(introducerId, localAuthorId, remoteAuthorId, + getAuthMacInputs(introducerId, localAuthorId, remoteAuthorId, acceptTimestamp, remoteAcceptTimestamp, ephemeralPublicKey, remoteEphemeralPublicKey, transportProperties, remoteTransportProperties, alice); @@ -138,19 +144,20 @@ class IntroductionCryptoImpl implements IntroductionCrypto { @Override @SuppressWarnings("ConstantConditions") - public void verifyMac(byte[] mac, IntroduceeSession s, + public void verifyAuthMac(byte[] mac, IntroduceeSession s, AuthorId localAuthorId) throws GeneralSecurityException { - boolean alice = isAlice(localAuthorId, s.getRemoteAuthor().getId()); - verifyMac(mac, new SecretKey(s.getMasterKey()), + boolean alice = isAlice(localAuthorId, s.getRemote().author.getId()); + verifyAuthMac(mac, new SecretKey(s.getRemote().macKey), s.getIntroducer().getId(), localAuthorId, - s.getRemoteAuthor().getId(), s.getAcceptTimestamp(), - s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(), - s.getRemotePublicKey(), s.getTransportProperties(), - s.getRemoteTransportProperties(), !alice); + s.getRemote().author.getId(), s.getLocal().acceptTimestamp, + s.getRemote().acceptTimestamp, s.getLocal().ephemeralPublicKey, + s.getRemote().ephemeralPublicKey, + s.getLocal().transportProperties, + s.getRemote().transportProperties, !alice); } - void verifyMac(byte[] mac, SecretKey masterKey, + void verifyAuthMac(byte[] mac, SecretKey macKey, AuthorId introducerId, AuthorId localAuthorId, AuthorId remoteAuthorId, long acceptTimestamp, long remoteAcceptTimestamp, byte[] ephemeralPublicKey, @@ -158,9 +165,8 @@ class IntroductionCryptoImpl implements IntroductionCrypto { Map transportProperties, Map remoteTransportProperties, boolean alice) throws GeneralSecurityException { - SecretKey macKey = deriveMacKey(masterKey, alice); byte[] inputs = - getMacInputs(introducerId, localAuthorId, remoteAuthorId, + getAuthMacInputs(introducerId, localAuthorId, remoteAuthorId, acceptTimestamp, remoteAcceptTimestamp, ephemeralPublicKey, remoteEphemeralPublicKey, transportProperties, remoteTransportProperties, !alice); @@ -169,7 +175,7 @@ class IntroductionCryptoImpl implements IntroductionCrypto { } } - private byte[] getMacInputs(AuthorId introducerId, + private byte[] getAuthMacInputs(AuthorId introducerId, AuthorId localAuthorId, AuthorId remoteAuthorId, long acceptTimestamp, long remoteAcceptTimestamp, byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey, @@ -214,9 +220,8 @@ class IntroductionCryptoImpl implements IntroductionCrypto { @SuppressWarnings("ConstantConditions") public void verifySignature(byte[] signature, IntroduceeSession s, AuthorId localAuthorId) throws GeneralSecurityException { - boolean alice = isAlice(s.getRemoteAuthor().getId(), localAuthorId); - SecretKey macKey = deriveMacKey(new SecretKey(s.getMasterKey()), alice); - verifySignature(macKey, s.getRemoteAuthor().getPublicKey(), signature); + SecretKey macKey = new SecretKey(s.getRemote().macKey); + verifySignature(macKey, s.getRemote().author.getPublicKey(), signature); } void verifySignature(SecretKey macKey, byte[] publicKey, @@ -232,4 +237,33 @@ class IntroductionCryptoImpl implements IntroductionCrypto { return crypto.mac(LABEL_AUTH_NONCE, macKey); } + @Override + public byte[] activateMac(IntroduceeSession 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, IntroduceeSession 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/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index 959d1d92b..d14bd46c7 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -190,8 +190,10 @@ class IntroductionManagerImpl extends ConversationClientImpl Author remote = messageParser.parseRequestMessage(m, body).getAuthor(); if (local.equals(remote)) throw new FormatException(); SessionId sessionId = crypto.getSessionId(introducer, local, remote); + boolean alice = crypto.isAlice(local.getId(), remote.getId()); return IntroduceeSession - .getInitial(m.getGroupId(), sessionId, introducer, remote); + .getInitial(m.getGroupId(), sessionId, introducer, alice, + remote); } private S handleMessage(Transaction txn, Message m, @@ -441,7 +443,7 @@ class IntroductionManagerImpl extends ConversationClientImpl IntroduceeSession session = sessionParser .parseIntroduceeSession(contactGroupId, bdfSession); sessionId = session.getSessionId(); - author = session.getRemoteAuthor(); + author = session.getRemote().author; } else throw new AssertionError(); Message msg = clientHelper.getMessage(txn, m); BdfList body = clientHelper.getMessageAsList(txn, m); @@ -481,7 +483,7 @@ class IntroductionManagerImpl extends ConversationClientImpl IntroduceeSession session = sessionParser .parseIntroduceeSession(contactGroupId, bdfSession); sessionId = session.getSessionId(); - author = session.getRemoteAuthor(); + author = session.getRemote().author; } else throw new AssertionError(); return new IntroductionResponse(sessionId, m, contactGroupId, role, meta.getTimestamp(), meta.isLocal(), status.isSent(), diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java index 43027b40b..6b59a64ae 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java @@ -27,6 +27,7 @@ import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; import static org.briarproject.briar.introduction.MessageType.ACCEPT; +import static org.briarproject.briar.introduction.MessageType.ACTIVATE; import static org.briarproject.briar.introduction.MessageType.AUTH; @@ -55,8 +56,9 @@ class IntroductionValidator extends BdfMessageValidator { return validateAcceptMessage(m, body); case AUTH: return validateAuthMessage(m, body); - case DECLINE: case ACTIVATE: + return validateActivateMessage(m, body); + case DECLINE: case ABORT: return validateOtherMessage(type, m, body); default: @@ -149,6 +151,32 @@ class IntroductionValidator extends BdfMessageValidator { Collections.singletonList(dependency)); } + private BdfMessageContext validateActivateMessage(Message m, BdfList body) + throws FormatException { + checkSize(body, 4); + + byte[] sessionIdBytes = body.getRaw(1); + checkLength(sessionIdBytes, UniqueId.LENGTH); + + byte[] previousMessageId = body.getOptionalRaw(2); + checkLength(previousMessageId, UniqueId.LENGTH); + + byte[] mac = body.getOptionalRaw(3); + checkLength(mac, MAC_BYTES); + + SessionId sessionId = new SessionId(sessionIdBytes); + BdfDictionary meta = messageEncoder + .encodeMetadata(ACTIVATE, sessionId, m.getTimestamp(), false, + false, false); + if (previousMessageId == null) { + return new BdfMessageContext(meta); + } else { + 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); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java index 42eb41c0f..2eed3057a 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java @@ -47,7 +47,8 @@ interface MessageEncoder { byte[] mac, byte[] signature); Message encodeActivateMessage(GroupId contactGroupId, long timestamp, - @Nullable MessageId previousMessageId, SessionId sessionId); + @Nullable MessageId previousMessageId, SessionId sessionId, + byte[] mac); Message encodeAbortMessage(GroupId contactGroupId, long timestamp, @Nullable MessageId previousMessageId, SessionId sessionId); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java index 861569ac5..89d3e40e5 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java @@ -100,12 +100,7 @@ class MessageEncoderImpl implements MessageEncoder { clientHelper.toList(author), message ); - try { - return messageFactory.createMessage(contactGroupId, timestamp, - clientHelper.toByteArray(body)); - } catch (FormatException e) { - throw new AssertionError(e); - } + return createMessage(contactGroupId, timestamp, body); } @Override @@ -119,14 +114,9 @@ class MessageEncoderImpl implements MessageEncoder { previousMessageId, ephemeralPublicKey, acceptTimestamp, - encodeTransportProperties(transportProperties) + clientHelper.toDictionary(transportProperties) ); - try { - return messageFactory.createMessage(contactGroupId, timestamp, - clientHelper.toByteArray(body)); - } catch (FormatException e) { - throw new AssertionError(e); - } + return createMessage(contactGroupId, timestamp, body); } @Override @@ -147,19 +137,20 @@ class MessageEncoderImpl implements MessageEncoder { mac, signature ); - try { - return messageFactory.createMessage(contactGroupId, timestamp, - clientHelper.toByteArray(body)); - } catch (FormatException e) { - throw new AssertionError(e); - } + return createMessage(contactGroupId, timestamp, body); } @Override public Message encodeActivateMessage(GroupId contactGroupId, long timestamp, - @Nullable MessageId previousMessageId, SessionId sessionId) { - return encodeMessage(ACTIVATE, contactGroupId, sessionId, timestamp, - previousMessageId); + @Nullable MessageId previousMessageId, SessionId sessionId, + byte[] mac) { + BdfList body = BdfList.of( + ACTIVATE.getValue(), + sessionId, + previousMessageId, + mac + ); + return createMessage(contactGroupId, timestamp, body); } @Override @@ -177,6 +168,11 @@ class MessageEncoderImpl implements MessageEncoder { 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)); @@ -185,13 +181,4 @@ class MessageEncoderImpl implements MessageEncoder { } } - private BdfDictionary encodeTransportProperties( - Map map) { - BdfDictionary d = new BdfDictionary(); - for (Map.Entry e : map.entrySet()) { - d.put(e.getKey().getString(), e.getValue()); - } - return d; - } - } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java index ef97282dd..69ddd242d 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java @@ -124,8 +124,9 @@ class MessageParserImpl implements MessageParser { SessionId sessionId = new SessionId(body.getRaw(1)); byte[] previousMsgBytes = body.getRaw(2); MessageId previousMessageId = new MessageId(previousMsgBytes); + byte[] mac = body.getRaw(3); return new ActivateMessage(m.getId(), m.getGroupId(), m.getTimestamp(), - previousMessageId, sessionId); + previousMessageId, sessionId, mac); } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/Session.java b/briar-core/src/main/java/org/briarproject/briar/introduction/Session.java index ddf04d044..086dfb1a2 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/Session.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/Session.java @@ -12,7 +12,7 @@ abstract class Session { private final SessionId sessionId; private final S state; - private long requestTimestamp; + private final long requestTimestamp; Session(SessionId sessionId, S state, long requestTimestamp) { this.sessionId = sessionId; @@ -30,7 +30,7 @@ abstract class Session { return state; } - public long getRequestTimestamp() { + long getRequestTimestamp() { return requestTimestamp; } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java index 2ffee61d8..9023f0d94 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java @@ -7,6 +7,9 @@ import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.briar.introduction.IntroduceeSession.Common; +import org.briarproject.briar.introduction.IntroduceeSession.Local; +import org.briarproject.briar.introduction.IntroduceeSession.Remote; import org.briarproject.briar.introduction.IntroducerSession.Introducee; import java.util.Map; @@ -19,6 +22,7 @@ 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.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ALICE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; @@ -28,12 +32,12 @@ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MAC_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID; @@ -91,34 +95,43 @@ class SessionEncoderImpl implements SessionEncoder { @Override public BdfDictionary encodeIntroduceeSession(IntroduceeSession s) { BdfDictionary d = encodeSession(s); - d.put(SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp()); - putNullable(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID, - s.getLastLocalMessageId()); - putNullable(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID, - s.getLastRemoteMessageId()); d.put(SESSION_KEY_INTRODUCER, clientHelper.toList(s.getIntroducer())); - d.put(SESSION_KEY_REMOTE_AUTHOR, - clientHelper.toList(s.getRemoteAuthor())); - putNullable(d, SESSION_KEY_EPHEMERAL_PUBLIC_KEY, - s.getEphemeralPublicKey()); - putNullable(d, SESSION_KEY_EPHEMERAL_PRIVATE_KEY, - s.getEphemeralPrivateKey()); - putNullable(d, SESSION_KEY_TRANSPORT_PROPERTIES, - s.getTransportProperties() == null ? null : - clientHelper.toDictionary(s.getTransportProperties())); - d.put(SESSION_KEY_ACCEPT_TIMESTAMP, s.getAcceptTimestamp()); + 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_REMOTE_EPHEMERAL_PUBLIC_KEY, - s.getRemotePublicKey()); - putNullable(d, SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES, - s.getRemoteTransportProperties() == null ? null : clientHelper - .toDictionary(s.getRemoteTransportProperties())); - d.put(SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP, s.getRemoteAcceptTimestamp()); 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); + putNullable(d, SESSION_KEY_TRANSPORT_PROPERTIES, + s.transportProperties == null ? null : + clientHelper.toDictionary(s.transportProperties)); + 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()); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java index 7c5b21925..52c12e547 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java @@ -13,6 +13,8 @@ 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.introduction.Role; +import org.briarproject.briar.introduction.IntroduceeSession.Local; +import org.briarproject.briar.introduction.IntroduceeSession.Remote; import org.briarproject.briar.introduction.IntroducerSession.Introducee; import java.util.HashMap; @@ -22,7 +24,10 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; +import static org.briarproject.briar.api.introduction.Role.INTRODUCER; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ALICE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; @@ -32,20 +37,18 @@ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MAC_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP; +import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY; -import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_STATE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES; -import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; -import static org.briarproject.briar.api.introduction.Role.INTRODUCER; @Immutable @NotNullByDefault @@ -103,42 +106,55 @@ class SessionParserImpl implements SessionParser { SessionId sessionId = getSessionId(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 transportKeys = parseTransportKeys( + d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS)); + return new IntroduceeSession(sessionId, state, requestTimestamp, + introducerGroupId, introducer, local, remote, + masterKey, transportKeys); + } + + 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); - MessageId lastRemoteMessageId = - getMessageId(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID); - Author introducer = getAuthor(d, SESSION_KEY_INTRODUCER); 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 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, transportProperties, + 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 transportProperties = tpDict == null ? null : clientHelper .parseAndValidateTransportPropertiesMap(tpDict); long acceptTimestamp = d.getLong(SESSION_KEY_ACCEPT_TIMESTAMP); - byte[] masterKey = d.getOptionalRaw(SESSION_KEY_MASTER_KEY); - Author remoteAuthor = getAuthor(d, SESSION_KEY_REMOTE_AUTHOR); - byte[] remoteEphemeralPublicKey = - d.getOptionalRaw(SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY); - BdfDictionary rptDict = d.getOptionalDictionary( - SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES); - Map remoteTransportProperties = - rptDict == null ? null : clientHelper - .parseAndValidateTransportPropertiesMap(rptDict); - long remoteAcceptTimestamp = - d.getLong(SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP); - Map transportKeys = parseTransportKeys( - d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS)); - return new IntroduceeSession(sessionId, state, requestTimestamp, - introducerGroupId, lastLocalMessageId, localTimestamp, - lastRemoteMessageId, introducer, ephemeralPublicKey, - ephemeralPrivateKey, transportProperties, acceptTimestamp, - masterKey, remoteAuthor, remoteEphemeralPublicKey, - remoteTransportProperties, remoteAcceptTimestamp, - transportKeys); + 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 { diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java index b8c636400..59cf3b4f4 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java @@ -96,35 +96,35 @@ public class IntroductionCryptoImplTest extends BrambleTestCase { } @Test - public void testAliceMac() throws Exception { + public void testAliceAuthMac() throws Exception { SecretKey aliceMacKey = crypto.deriveMacKey(masterKey, true); byte[] aliceMac = - crypto.mac(aliceMacKey, introducer.getId(), alice.getId(), + crypto.authMac(aliceMacKey, introducer.getId(), alice.getId(), bob.getId(), aliceAcceptTimestamp, bobAcceptTimestamp, aliceEphemeral.getPublic().getEncoded(), bobEphemeral.getPublic().getEncoded(), aliceTransport, bobTransport, true); - crypto.verifyMac(aliceMac, masterKey, introducer.getId(), bob.getId(), - alice.getId(), bobAcceptTimestamp, aliceAcceptTimestamp, - bobEphemeral.getPublic().getEncoded(), + crypto.verifyAuthMac(aliceMac, aliceMacKey, introducer.getId(), + bob.getId(), alice.getId(), bobAcceptTimestamp, + aliceAcceptTimestamp, bobEphemeral.getPublic().getEncoded(), aliceEphemeral.getPublic().getEncoded(), bobTransport, aliceTransport, true); } @Test - public void testBobMac() throws Exception { + public void testBobAuthMac() throws Exception { SecretKey bobMacKey = crypto.deriveMacKey(masterKey, false); byte[] bobMac = - crypto.mac(bobMacKey, introducer.getId(), bob.getId(), + crypto.authMac(bobMacKey, introducer.getId(), bob.getId(), alice.getId(), bobAcceptTimestamp, aliceAcceptTimestamp, bobEphemeral.getPublic().getEncoded(), aliceEphemeral.getPublic().getEncoded(), bobTransport, aliceTransport, false); - crypto.verifyMac(bobMac, masterKey, introducer.getId(), alice.getId(), - bob.getId(), aliceAcceptTimestamp, bobAcceptTimestamp, - aliceEphemeral.getPublic().getEncoded(), + crypto.verifyAuthMac(bobMac, bobMacKey, introducer.getId(), + alice.getId(), bob.getId(), aliceAcceptTimestamp, + bobAcceptTimestamp, aliceEphemeral.getPublic().getEncoded(), bobEphemeral.getPublic().getEncoded(), aliceTransport, bobTransport, false); } @@ -139,4 +139,20 @@ public class IntroductionCryptoImplTest extends BrambleTestCase { signature); } + @Test + public void testAliceActivateMac() throws Exception { + SecretKey aliceMacKey = crypto.deriveMacKey(masterKey, true); + byte[] aliceMac = crypto.activateMac(aliceMacKey); + + crypto.verifyActivateMac(aliceMac, aliceMacKey); + } + + @Test + public void testBobActivateMac() throws Exception { + SecretKey bobMacKey = crypto.deriveMacKey(masterKey, false); + byte[] bobMac = crypto.activateMac(bobMacKey); + + crypto.verifyActivateMac(bobMac, bobMacKey); + } + } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 1adfb9207..3dccbbc8d 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -170,16 +170,6 @@ public class IntroductionIntegrationTest sync1To0(1, true); sync0To2(1, true); - // assert that introducee2 added introducee1 - Contact contact1From2 = c2.getContactManager() - .getContact(author1.getId(), author2.getId()); - - // assert that introducee2 did add transport properties - // TODO check when notion of inactive contacts has been removed -// TransportProperties tp2 = c2.getTransportPropertyManager() -// .getRemoteProperties(contact1From2.getId(), TRANSPORT_ID); -// assertFalse(tp2.isEmpty()); - // assert that introducee2 did add the transport keys IntroduceeSession session2 = getIntroduceeSession(c2.getClientHelper(), introductionManager2.getContactGroup(contact0From2).getId()); @@ -194,7 +184,7 @@ public class IntroductionIntegrationTest IntroduceeSession session1 = getIntroduceeSession(c1.getClientHelper(), introductionManager1.getContactGroup(contact0From1).getId()); assertNull(session1.getMasterKey()); - assertNull(session1.getEphemeralPrivateKey()); + assertNull(session1.getLocal().ephemeralPrivateKey); assertNull(session1.getTransportKeys()); // sync second ACTIVATE and its forward @@ -533,16 +523,6 @@ public class IntroductionIntegrationTest sync1To0(1, true); sync0To2(1, true); - // assert that introducee2 did not add any transport properties - TransportProperties tp2 = c2.getTransportPropertyManager() - .getRemoteProperties(contactId1From2, TRANSPORT_ID); - assertTrue(tp2.isEmpty()); - - // assert that introducee2 did not add any transport keys - IntroduceeSession session2 = getIntroduceeSession(c2.getClientHelper(), - introductionManager2.getContactGroup(contact0From2).getId()); - assertNull(session2.getTransportKeys()); - // sync second AUTH and its forward as well as the following ACTIVATE sync2To0(2, true); sync0To1(2, true); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java index fbebeece4..7614121cf 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java @@ -312,7 +312,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase { @Test public void testAcceptsActivate() throws Exception { BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), - previousMsgId.getBytes()); + previousMsgId.getBytes(), mac); expectEncodeMetadata(ACTIVATE); BdfMessageContext messageContext = @@ -323,27 +323,37 @@ public class IntroductionValidatorTest extends ValidatorTestCase { @Test(expected = FormatException.class) public void testRejectsTooShortBodyForActivate() throws Exception { - BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes()); + BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), + previousMsgId.getBytes()); validator.validateMessage(message, group, body); } @Test(expected = FormatException.class) public void testRejectsTooLongBodyForActivate() throws Exception { BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), - previousMsgId.getBytes(), null); + previousMsgId.getBytes(), mac, null); validator.validateMessage(message, group, body); } @Test(expected = FormatException.class) public void testRejectsInvalidSessionIdForActivate() throws Exception { BdfList body = - BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes()); + BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes(), + mac); validator.validateMessage(message, group, body); } @Test(expected = FormatException.class) public void testRejectsInvalidPreviousMsgIdForActivate() throws Exception { - BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), 1); + BdfList body = + BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), 1, mac); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsInvalidMacForActivate() throws Exception { + BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), getRandomBytes(MAC_BYTES - 1)); validator.validateMessage(message, group, body); } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java index a4c155897..f3480f158 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java @@ -165,17 +165,17 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { sessionId, ephemeralPublicKey, acceptTimestamp, transportProperties); validator.validateMessage(m, group, clientHelper.toList(m)); - AcceptMessage rm = + AcceptMessage am = messageParser.parseAcceptMessage(m, clientHelper.toList(m)); - assertEquals(m.getId(), rm.getMessageId()); - assertEquals(m.getGroupId(), rm.getGroupId()); - assertEquals(m.getTimestamp(), rm.getTimestamp()); - assertEquals(previousMsgId, rm.getPreviousMessageId()); - assertEquals(sessionId, rm.getSessionId()); - assertArrayEquals(ephemeralPublicKey, rm.getEphemeralPublicKey()); - assertEquals(acceptTimestamp, rm.getAcceptTimestamp()); - assertEquals(transportProperties, rm.getTransportProperties()); + assertEquals(m.getId(), am.getMessageId()); + assertEquals(m.getGroupId(), am.getGroupId()); + assertEquals(m.getTimestamp(), am.getTimestamp()); + assertEquals(previousMsgId, am.getPreviousMessageId()); + assertEquals(sessionId, am.getSessionId()); + assertArrayEquals(ephemeralPublicKey, am.getEphemeralPublicKey()); + assertEquals(acceptTimestamp, am.getAcceptTimestamp()); + assertEquals(transportProperties, am.getTransportProperties()); } @Test @@ -184,14 +184,14 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { .encodeDeclineMessage(groupId, timestamp, previousMsgId, sessionId); validator.validateMessage(m, group, clientHelper.toList(m)); - DeclineMessage rm = + DeclineMessage dm = messageParser.parseDeclineMessage(m, clientHelper.toList(m)); - assertEquals(m.getId(), rm.getMessageId()); - assertEquals(m.getGroupId(), rm.getGroupId()); - assertEquals(m.getTimestamp(), rm.getTimestamp()); - assertEquals(previousMsgId, rm.getPreviousMessageId()); - assertEquals(sessionId, rm.getSessionId()); + assertEquals(m.getId(), dm.getMessageId()); + assertEquals(m.getGroupId(), dm.getGroupId()); + assertEquals(m.getTimestamp(), dm.getTimestamp()); + assertEquals(previousMsgId, dm.getPreviousMessageId()); + assertEquals(sessionId, dm.getSessionId()); } @Test @@ -200,32 +200,33 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { .encodeAuthMessage(groupId, timestamp, previousMsgId, sessionId, mac, signature); validator.validateMessage(m, group, clientHelper.toList(m)); - AuthMessage rm = + AuthMessage am = messageParser.parseAuthMessage(m, clientHelper.toList(m)); - assertEquals(m.getId(), rm.getMessageId()); - assertEquals(m.getGroupId(), rm.getGroupId()); - assertEquals(m.getTimestamp(), rm.getTimestamp()); - assertEquals(previousMsgId, rm.getPreviousMessageId()); - assertEquals(sessionId, rm.getSessionId()); - assertArrayEquals(mac, rm.getMac()); - assertArrayEquals(signature, rm.getSignature()); + assertEquals(m.getId(), am.getMessageId()); + assertEquals(m.getGroupId(), am.getGroupId()); + assertEquals(m.getTimestamp(), am.getTimestamp()); + assertEquals(previousMsgId, am.getPreviousMessageId()); + assertEquals(sessionId, am.getSessionId()); + assertArrayEquals(mac, am.getMac()); + assertArrayEquals(signature, am.getSignature()); } @Test public void testActivateMessage() throws Exception { Message m = messageEncoder .encodeActivateMessage(groupId, timestamp, previousMsgId, - sessionId); + sessionId, mac); validator.validateMessage(m, group, clientHelper.toList(m)); - ActivateMessage rm = + ActivateMessage am = messageParser.parseActivateMessage(m, clientHelper.toList(m)); - assertEquals(m.getId(), rm.getMessageId()); - assertEquals(m.getGroupId(), rm.getGroupId()); - assertEquals(m.getTimestamp(), rm.getTimestamp()); - assertEquals(previousMsgId, rm.getPreviousMessageId()); - assertEquals(sessionId, rm.getSessionId()); + assertEquals(m.getId(), am.getMessageId()); + assertEquals(m.getGroupId(), am.getGroupId()); + assertEquals(m.getTimestamp(), am.getTimestamp()); + assertEquals(previousMsgId, am.getPreviousMessageId()); + assertEquals(sessionId, am.getSessionId()); + assertArrayEquals(mac, am.getMac()); } @Test @@ -234,14 +235,14 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { .encodeAbortMessage(groupId, timestamp, previousMsgId, sessionId); validator.validateMessage(m, group, clientHelper.toList(m)); - AbortMessage rm = + AbortMessage am = messageParser.parseAbortMessage(m, clientHelper.toList(m)); - assertEquals(m.getId(), rm.getMessageId()); - assertEquals(m.getGroupId(), rm.getGroupId()); - assertEquals(m.getTimestamp(), rm.getTimestamp()); - assertEquals(previousMsgId, rm.getPreviousMessageId()); - assertEquals(sessionId, rm.getSessionId()); + assertEquals(m.getId(), am.getMessageId()); + assertEquals(m.getGroupId(), am.getGroupId()); + assertEquals(m.getTimestamp(), am.getTimestamp()); + assertEquals(previousMsgId, am.getPreviousMessageId()); + assertEquals(sessionId, am.getSessionId()); } } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java index 9d773bac0..ffbcaa943 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java @@ -28,15 +28,19 @@ import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; -import static org.briarproject.bramble.util.StringUtils.getRandomString; +import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; +import static org.briarproject.briar.api.introduction.Role.INTRODUCER; +import static org.briarproject.briar.introduction.IntroduceeSession.Local; +import static org.briarproject.briar.introduction.IntroduceeSession.Remote; import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTHS; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE; -import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; -import static org.briarproject.briar.api.introduction.Role.INTRODUCER; +import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class SessionEncoderParserIntegrationTest extends BrambleTestCase { @@ -74,6 +78,8 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { private final Map remoteTransportProperties = getTransportPropertiesMap(3); private final Map transportKeys = new HashMap<>(); + private final byte[] localMacKey = getRandomBytes(SecretKey.LENGTH); + private final byte[] remoteMacKey = getRandomBytes(SecretKey.LENGTH); public SessionEncoderParserIntegrationTest() { BriarIntegrationTestComponent component = @@ -82,8 +88,8 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { sessionEncoder = new SessionEncoderImpl(clientHelper); sessionParser = new SessionParserImpl(clientHelper); - author1 = getRealAuthor(); - author2 = getRealAuthor(); + author1 = getRealAuthor(authorFactory); + author2 = getRealAuthor(authorFactory); transportKeys.put(getTransportId(), new KeySetId(1)); transportKeys.put(getTransportId(), new KeySetId(2)); transportKeys.put(getTransportId(), new KeySetId(3)); @@ -167,48 +173,70 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { assertEquals(s1.getSessionId(), s2.getSessionId()); assertEquals(groupId1, s1.getContactGroupId()); assertEquals(s1.getContactGroupId(), s2.getContactGroupId()); + assertEquals(author1, s1.getIntroducer()); + assertEquals(s1.getIntroducer(), s2.getIntroducer()); + assertArrayEquals(masterKey, s1.getMasterKey()); + assertArrayEquals(s1.getMasterKey(), s2.getMasterKey()); + assertEquals(transportKeys, s1.getTransportKeys()); + assertEquals(s1.getTransportKeys(), s2.getTransportKeys()); assertEquals(localTimestamp, s1.getLocalTimestamp()); assertEquals(s1.getLocalTimestamp(), s2.getLocalTimestamp()); assertEquals(lastLocalMessageId, s1.getLastLocalMessageId()); assertEquals(s1.getLastLocalMessageId(), s2.getLastLocalMessageId()); assertEquals(lastRemoteMessageId, s1.getLastRemoteMessageId()); assertEquals(s1.getLastRemoteMessageId(), s2.getLastRemoteMessageId()); - assertEquals(author1, s1.getIntroducer()); - assertEquals(s1.getIntroducer(), s2.getIntroducer()); - assertEquals(author2, s1.getRemoteAuthor()); - assertEquals(s1.getRemoteAuthor(), s2.getRemoteAuthor()); - assertArrayEquals(ephemeralPublicKey, s1.getEphemeralPublicKey()); - assertArrayEquals(s1.getEphemeralPublicKey(), - s2.getEphemeralPublicKey()); - assertArrayEquals(ephemeralPrivateKey, s1.getEphemeralPrivateKey()); - assertArrayEquals(s1.getEphemeralPrivateKey(), - s2.getEphemeralPrivateKey()); - assertEquals(acceptTimestamp, s1.getAcceptTimestamp()); - assertEquals(s1.getAcceptTimestamp(), s2.getAcceptTimestamp()); - assertArrayEquals(masterKey, s1.getMasterKey()); - assertArrayEquals(s1.getMasterKey(), s2.getMasterKey()); - assertArrayEquals(remoteEphemeralPublicKey, s1.getRemotePublicKey()); - assertArrayEquals(s1.getRemotePublicKey(), - s2.getRemotePublicKey()); - assertEquals(transportProperties, s1.getTransportProperties()); - assertEquals(s1.getTransportProperties(), s2.getTransportProperties()); + + // check local + assertTrue(s1.getLocal().alice); + assertEquals(s1.getLocal().alice, s2.getLocal().alice); + assertEquals(lastLocalMessageId, s1.getLocal().lastMessageId); + assertEquals(s1.getLocal().lastMessageId, s2.getLocal().lastMessageId); + assertEquals(localTimestamp, s1.getLocal().lastMessageTimestamp); + assertEquals(s1.getLocal().lastMessageTimestamp, + s2.getLocal().lastMessageTimestamp); + assertArrayEquals(ephemeralPublicKey, s1.getLocal().ephemeralPublicKey); + assertArrayEquals(s1.getLocal().ephemeralPublicKey, + s2.getLocal().ephemeralPublicKey); + assertArrayEquals(ephemeralPrivateKey, + s1.getLocal().ephemeralPrivateKey); + assertArrayEquals(s1.getLocal().ephemeralPrivateKey, + s2.getLocal().ephemeralPrivateKey); + assertEquals(transportProperties, s1.getLocal().transportProperties); + assertEquals(s1.getLocal().transportProperties, + s2.getLocal().transportProperties); + assertEquals(acceptTimestamp, s1.getLocal().acceptTimestamp); + assertEquals(s1.getLocal().acceptTimestamp, + s2.getLocal().acceptTimestamp); + assertArrayEquals(localMacKey, s1.getLocal().macKey); + assertArrayEquals(s1.getLocal().macKey, s2.getLocal().macKey); + + // check remote + assertFalse(s1.getRemote().alice); + assertEquals(s1.getRemote().alice, s2.getRemote().alice); + assertEquals(author2, s1.getRemote().author); + assertEquals(s1.getRemote().author, s2.getRemote().author); + assertEquals(lastRemoteMessageId, s1.getRemote().lastMessageId); + assertEquals(s1.getRemote().lastMessageId, + s2.getRemote().lastMessageId); + assertArrayEquals(remoteEphemeralPublicKey, + s1.getRemote().ephemeralPublicKey); + assertArrayEquals(s1.getRemote().ephemeralPublicKey, + s2.getRemote().ephemeralPublicKey); assertEquals(remoteTransportProperties, - s1.getRemoteTransportProperties()); - assertEquals(s1.getRemoteTransportProperties(), - s2.getRemoteTransportProperties()); - assertEquals(remoteAcceptTimestamp, s1.getRemoteAcceptTimestamp()); - assertEquals(s1.getRemoteAcceptTimestamp(), s2.getRemoteAcceptTimestamp()); - assertEquals(transportKeys, s1.getTransportKeys()); - assertEquals(s1.getTransportKeys(), s2.getTransportKeys()); + s1.getRemote().transportProperties); + assertEquals(s1.getRemote().transportProperties, + s2.getRemote().transportProperties); + assertEquals(remoteAcceptTimestamp, s1.getRemote().acceptTimestamp); + assertEquals(s1.getRemote().acceptTimestamp, + s2.getRemote().acceptTimestamp); + assertArrayEquals(remoteMacKey, s1.getRemote().macKey); + assertArrayEquals(s1.getRemote().macKey, s2.getRemote().macKey); } @Test public void testIntroduceeSessionWithNulls() throws FormatException { - IntroduceeSession s1 = - new IntroduceeSession(sessionId, LOCAL_ACCEPTED, - requestTimestamp, groupId1, null, localTimestamp, null, - author1, null, null, null, acceptTimestamp, null, - author2, null, null, remoteAcceptTimestamp, null); + IntroduceeSession s1 = IntroduceeSession + .getInitial(groupId1, sessionId, author1, false, author2); BdfDictionary d = sessionEncoder.encodeIntroduceeSession(s1); IntroduceeSession s2 = @@ -218,14 +246,38 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { assertEquals(s1.getLastLocalMessageId(), s2.getLastLocalMessageId()); assertNull(s1.getLastRemoteMessageId()); assertEquals(s1.getLastRemoteMessageId(), s2.getLastRemoteMessageId()); - assertNull(s1.getEphemeralPublicKey()); - assertArrayEquals(s1.getEphemeralPublicKey(), - s2.getEphemeralPublicKey()); - assertNull(s1.getEphemeralPrivateKey()); - assertArrayEquals(s1.getEphemeralPrivateKey(), - s2.getEphemeralPrivateKey()); + assertNull(s1.getMasterKey()); + assertEquals(s1.getMasterKey(), s2.getMasterKey()); assertNull(s1.getTransportKeys()); assertEquals(s1.getTransportKeys(), s2.getTransportKeys()); + + // check local + assertNull(s1.getLocal().lastMessageId); + assertEquals(s1.getLocal().lastMessageId, s2.getLocal().lastMessageId); + assertNull(s1.getLocal().ephemeralPublicKey); + assertEquals(s1.getLocal().ephemeralPublicKey, + s2.getLocal().ephemeralPublicKey); + assertNull(s1.getLocal().ephemeralPrivateKey); + assertEquals(s1.getLocal().ephemeralPrivateKey, + s2.getLocal().ephemeralPrivateKey); + assertNull(s1.getLocal().transportProperties); + assertEquals(s1.getLocal().transportProperties, + s2.getLocal().transportProperties); + assertNull(s1.getLocal().macKey); + assertEquals(s1.getLocal().macKey, s2.getLocal().macKey); + + // check remote + assertNull(s1.getRemote().lastMessageId); + assertEquals(s1.getRemote().lastMessageId, + s2.getRemote().lastMessageId); + assertNull(s1.getRemote().ephemeralPublicKey); + assertEquals(s1.getRemote().ephemeralPublicKey, + s2.getRemote().ephemeralPublicKey); + assertNull(s1.getRemote().transportProperties); + assertEquals(s1.getRemote().transportProperties, + s2.getRemote().transportProperties); + assertNull(s1.getRemote().macKey); + assertEquals(s1.getRemote().macKey, s2.getRemote().macKey); } @Test(expected = FormatException.class) @@ -256,13 +308,15 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { } private IntroduceeSession getIntroduceeSession() { + Local local = new Local(true, lastLocalMessageId, localTimestamp, + ephemeralPublicKey, ephemeralPrivateKey, transportProperties, + acceptTimestamp, localMacKey); + Remote remote = new Remote(false, author2, lastRemoteMessageId, + remoteEphemeralPublicKey, remoteTransportProperties, + remoteAcceptTimestamp, remoteMacKey); return new IntroduceeSession(sessionId, LOCAL_ACCEPTED, - requestTimestamp, groupId1, lastLocalMessageId, localTimestamp, - lastRemoteMessageId, author1, ephemeralPublicKey, - ephemeralPrivateKey, transportProperties, acceptTimestamp, - masterKey, author2, remoteEphemeralPublicKey, - remoteTransportProperties, remoteAcceptTimestamp, - transportKeys); + requestTimestamp, groupId1, author1, local, remote, + masterKey, transportKeys); } private void assertIntroduceeEquals(Introducee i1, Introducee i2) { @@ -273,9 +327,4 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { assertEquals(i1.lastRemoteMessageId, i2.lastRemoteMessageId); } - private Author getRealAuthor() { - return authorFactory.createAuthor(getRandomString(5), - getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); - } - } From f8f98ed95dbd14845d931e31bd0b4951da44428b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 26 Apr 2018 18:00:57 -0300 Subject: [PATCH 17/21] Properly handle DECLINE messages in START state Previously, DECLINE messages let directly to the START state for introducer and introducees. So incoming ACCEPT and DECLINE messages needed to be ignored in START state introducing undefined behavior into the protocol. This is fixed with this commit by adding two additional states to the introducer state machine as well as making use of the existing LOCAL_DECLINED state for the introducees. --- .../introduction/AbstractProtocolEngine.java | 19 +++++ .../IntroduceeProtocolEngine.java | 76 ++++++++--------- .../briar/introduction/IntroduceeSession.java | 4 +- .../IntroducerProtocolEngine.java | 83 +++++++------------ .../briar/introduction/IntroducerState.java | 9 +- .../IntroductionIntegrationTest.java | 41 ++++++--- .../IntroductionIntegrationTestComponent.java | 1 + 7 files changed, 123 insertions(+), 110 deletions(-) diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java index 75af1b87c..c9002bb0e 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java @@ -3,12 +3,14 @@ package org.briarproject.briar.introduction; 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.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.AuthorId; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; @@ -18,6 +20,8 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.introduction.IntroductionResponse; +import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent; import java.util.Map; @@ -141,6 +145,21 @@ abstract class AbstractProtocolEngine } } + void broadcastIntroductionResponseReceivedEvent(Transaction txn, + Session s, AuthorId sender, AbstractIntroductionMessage m) + throws DbException { + AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId(); + Contact c = contactManager.getContact(txn, sender, localAuthorId); + IntroductionResponse response = + new IntroductionResponse(s.getSessionId(), m.getMessageId(), + m.getGroupId(), s.getRole(), m.getTimestamp(), false, + false, false, false, c.getAuthor().getName(), + m instanceof AcceptMessage); + IntroductionResponseReceivedEvent e = + new IntroductionResponseReceivedEvent(c.getId(), response); + txn.attach(e); + } + void markMessageVisibleInUi(Transaction txn, MessageId m) throws DbException { BdfDictionary meta = new BdfDictionary(); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index ce7d5f76c..df0a71900 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -27,10 +27,8 @@ import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.introduction.IntroductionRequest; -import org.briarproject.briar.api.introduction.IntroductionResponse; import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent; -import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent; import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent; import java.security.GeneralSecurityException; @@ -44,7 +42,9 @@ import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES; import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED; +import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_DECLINED; import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_ACCEPTED; +import static org.briarproject.briar.introduction.IntroduceeState.START; @Immutable @NotNullByDefault @@ -142,12 +142,12 @@ class IntroduceeProtocolEngine public IntroduceeSession onAcceptMessage(Transaction txn, IntroduceeSession session, AcceptMessage m) throws DbException { switch (session.getState()) { - case START: - return onRemoteResponseInStart(txn, session, m); + case LOCAL_DECLINED: + return onRemoteResponseWhenDeclined(txn, session, m); case AWAIT_RESPONSES: case LOCAL_ACCEPTED: return onRemoteAccept(txn, session, m); - case LOCAL_DECLINED: + case START: case REMOTE_ACCEPTED: case AWAIT_AUTH: case AWAIT_ACTIVATE: @@ -161,12 +161,12 @@ class IntroduceeProtocolEngine public IntroduceeSession onDeclineMessage(Transaction txn, IntroduceeSession session, DeclineMessage m) throws DbException { switch (session.getState()) { - case START: - return onRemoteResponseInStart(txn, session, m); - case AWAIT_RESPONSES: case LOCAL_DECLINED: + return onRemoteResponseWhenDeclined(txn, session, m); + case AWAIT_RESPONSES: case LOCAL_ACCEPTED: return onRemoteDecline(txn, session, m); + case START: case REMOTE_ACCEPTED: case AWAIT_AUTH: case AWAIT_ACTIVATE: @@ -255,8 +255,7 @@ class IntroduceeProtocolEngine } private IntroduceeSession onLocalAccept(Transaction txn, - IntroduceeSession s, long timestamp) - throws DbException { + IntroduceeSession s, long timestamp) throws DbException { // Mark the request message unavailable to answer markRequestsUnavailableToAnswer(txn, s); @@ -291,20 +290,23 @@ class IntroduceeProtocolEngine } private IntroduceeSession onLocalDecline(Transaction txn, - IntroduceeSession s, long timestamp) - throws DbException { + IntroduceeSession s, long timestamp) throws DbException { // Mark the request message unavailable to answer markRequestsUnavailableToAnswer(txn, s); // Send a DECLINE message long localTimestamp = Math.max(timestamp + 1, getLocalTimestamp(s)); Message sent = sendDeclineMessage(txn, s, localTimestamp, true); + // Track the message messageTracker.trackOutgoingMessage(txn, sent); - // Move to the START state - return IntroduceeSession.clear(s, sent.getId(), sent.getTimestamp(), - s.getLastRemoteMessageId()); + // Move to the START or LOCAL_DECLINED state, if still awaiting response + IntroduceeState state = + s.getState() == REMOTE_ACCEPTED ? START : LOCAL_DECLINED; + return IntroduceeSession + .clear(s, state, sent.getId(), sent.getTimestamp(), + s.getLastRemoteMessageId()); } private IntroduceeSession onRemoteAccept(Transaction txn, @@ -347,25 +349,17 @@ class IntroduceeProtocolEngine .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); // Broadcast IntroductionResponseReceivedEvent - Contact c = contactManager.getContact(txn, s.getIntroducer().getId(), - identityManager.getLocalAuthor(txn).getId()); - IntroductionResponse request = - new IntroductionResponse(s.getSessionId(), m.getMessageId(), - m.getGroupId(), INTRODUCEE, m.getTimestamp(), false, - false, false, false, s.getRemote().author.getName(), - false); - IntroductionResponseReceivedEvent e = - new IntroductionResponseReceivedEvent(c.getId(), request); - txn.attach(e); + broadcastIntroductionResponseReceivedEvent(txn, s, + s.getIntroducer().getId(), m); // Move back to START state - return IntroduceeSession - .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), - m.getMessageId()); + return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(), + s.getLocalTimestamp(), m.getMessageId()); } - private IntroduceeSession onRemoteResponseInStart(Transaction txn, - IntroduceeSession s, AbstractIntroductionMessage m) throws DbException { + private IntroduceeSession onRemoteResponseWhenDeclined(Transaction txn, + IntroduceeSession s, AbstractIntroductionMessage m) + throws DbException { // The timestamp must be higher than the last request message if (m.getTimestamp() <= s.getRequestTimestamp()) return abort(txn, s); @@ -373,10 +367,9 @@ class IntroduceeProtocolEngine if (isInvalidDependency(s, m.getPreviousMessageId())) return abort(txn, s); - // Stay in START state - return IntroduceeSession - .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), - m.getMessageId()); + // Move to START state + return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(), + s.getLocalTimestamp(), m.getMessageId()); } private IntroduceeSession onLocalAuth(Transaction txn, IntroduceeSession s) @@ -479,9 +472,8 @@ class IntroduceeProtocolEngine keyManager.activateKeys(txn, s.getTransportKeys()); // Move back to START state - return IntroduceeSession - .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), - m.getMessageId()); + return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(), + s.getLocalTimestamp(), m.getMessageId()); } private IntroduceeSession onRemoteAbort(Transaction txn, @@ -494,9 +486,8 @@ class IntroduceeProtocolEngine txn.attach(new IntroductionAbortedEvent(s.getSessionId())); // Reset the session back to initial state - return IntroduceeSession - .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), - m.getMessageId()); + return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(), + s.getLocalTimestamp(), m.getMessageId()); } private IntroduceeSession abort(Transaction txn, IntroduceeSession s) @@ -511,8 +502,9 @@ class IntroduceeProtocolEngine txn.attach(new IntroductionAbortedEvent(s.getSessionId())); // Reset the session back to initial state - return IntroduceeSession.clear(s, sent.getId(), sent.getTimestamp(), - s.getLastRemoteMessageId()); + return IntroduceeSession + .clear(s, START, sent.getId(), sent.getTimestamp(), + s.getLastRemoteMessageId()); } private boolean isInvalidDependency(IntroduceeSession s, diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java index 92b06abc0..b952b3dc5 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java @@ -121,7 +121,7 @@ class IntroduceeSession extends Session remote, null, transportKeys); } - static IntroduceeSession clear(IntroduceeSession s, + static IntroduceeSession clear(IntroduceeSession s, IntroduceeState state, @Nullable MessageId lastLocalMessageId, long localTimestamp, @Nullable MessageId lastRemoteMessageId) { Local local = @@ -130,7 +130,7 @@ class IntroduceeSession extends Session Remote remote = new Remote(s.remote.alice, s.remote.author, lastRemoteMessageId, null, null, -1, null); - return new IntroduceeSession(s.getSessionId(), START, + return new IntroduceeSession(s.getSessionId(), state, s.getRequestTimestamp(), s.contactGroupId, s.introducer, local, remote, null, null); } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java index 7115ec089..8aeb28277 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java @@ -2,12 +2,11 @@ package org.briarproject.briar.introduction; 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.AuthorId; +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.sync.GroupId; @@ -16,16 +15,13 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.ProtocolStateException; -import org.briarproject.briar.api.introduction.IntroductionResponse; import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent; -import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent; import org.briarproject.briar.introduction.IntroducerSession.Introducee; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.briar.api.introduction.Role.INTRODUCER; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B; @@ -35,6 +31,8 @@ import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTH_B; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSES; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_A; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_B; +import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED; +import static org.briarproject.briar.introduction.IntroducerState.B_DECLINED; import static org.briarproject.briar.introduction.IntroducerState.START; @Immutable @@ -68,6 +66,8 @@ class IntroducerProtocolEngine case AWAIT_RESPONSES: case AWAIT_RESPONSE_A: case AWAIT_RESPONSE_B: + case A_DECLINED: + case B_DECLINED: case AWAIT_AUTHS: case AWAIT_AUTH_A: case AWAIT_AUTH_B: @@ -111,8 +111,10 @@ class IntroducerProtocolEngine case AWAIT_RESPONSE_A: case AWAIT_RESPONSE_B: return onRemoteAccept(txn, s, m); + case A_DECLINED: + case B_DECLINED: + return onRemoteResponseWhenDeclined(txn, s, m); case START: - return onRemoteResponseInStart(txn, s, m); case AWAIT_AUTHS: case AWAIT_AUTH_A: case AWAIT_AUTH_B: @@ -133,8 +135,10 @@ class IntroducerProtocolEngine case AWAIT_RESPONSE_A: case AWAIT_RESPONSE_B: return onRemoteDecline(txn, s, m); + case A_DECLINED: + case B_DECLINED: + return onRemoteResponseWhenDeclined(txn, s, m); case START: - return onRemoteResponseInStart(txn, s, m); case AWAIT_AUTHS: case AWAIT_AUTH_A: case AWAIT_AUTH_B: @@ -159,6 +163,8 @@ class IntroducerProtocolEngine case AWAIT_RESPONSES: case AWAIT_RESPONSE_A: case AWAIT_RESPONSE_B: + case A_DECLINED: + case B_DECLINED: case AWAIT_ACTIVATES: case AWAIT_ACTIVATE_A: case AWAIT_ACTIVATE_B: @@ -180,6 +186,8 @@ class IntroducerProtocolEngine case AWAIT_RESPONSES: case AWAIT_RESPONSE_A: case AWAIT_RESPONSE_B: + case A_DECLINED: + case B_DECLINED: case AWAIT_AUTHS: case AWAIT_AUTH_A: case AWAIT_AUTH_B: @@ -262,17 +270,8 @@ class IntroducerProtocolEngine } // Broadcast IntroductionResponseReceivedEvent - AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId(); - Contact c = contactManager.getContact(txn, - senderIsAlice ? introduceeA.author.getId() : - introduceeB.author.getId(), localAuthorId); - IntroductionResponse request = - new IntroductionResponse(s.getSessionId(), m.getMessageId(), - m.getGroupId(), INTRODUCER, m.getTimestamp(), false, - false, false, false, c.getAuthor().getName(), true); - IntroductionResponseReceivedEvent e = - new IntroductionResponseReceivedEvent(c.getId(), request); - txn.attach(e); + Author sender = senderIsAlice ? introduceeA.author : introduceeB.author; + broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m); // Move to the next state return new IntroducerSession(s.getSessionId(), state, @@ -312,34 +311,28 @@ class IntroducerProtocolEngine long timestamp = getLocalTimestamp(s, i); Message sent = sendDeclineMessage(txn, i, timestamp, false); - // Update introducee state + // Create the next state + IntroducerState state = START; Introducee introduceeA, introduceeB; if (senderIsAlice) { + if (s.getState() == AWAIT_RESPONSES) state = A_DECLINED; introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId()); introduceeB = new Introducee(s.getIntroduceeB(), sent); } else { + if (s.getState() == AWAIT_RESPONSES) state = B_DECLINED; introduceeA = new Introducee(s.getIntroduceeA(), sent); introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId()); } // Broadcast IntroductionResponseReceivedEvent - AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId(); - Contact c = contactManager.getContact(txn, - senderIsAlice ? introduceeA.author.getId() : - introduceeB.author.getId(), localAuthorId); - IntroductionResponse request = - new IntroductionResponse(s.getSessionId(), m.getMessageId(), - m.getGroupId(), INTRODUCER, m.getTimestamp(), false, - false, false, false, c.getAuthor().getName(), false); - IntroductionResponseReceivedEvent e = - new IntroductionResponseReceivedEvent(c.getId(), request); - txn.attach(e); + Author sender = senderIsAlice ? introduceeA.author : introduceeB.author; + broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m); - return new IntroducerSession(s.getSessionId(), START, + return new IntroducerSession(s.getSessionId(), state, s.getRequestTimestamp(), introduceeA, introduceeB); } - private IntroducerSession onRemoteResponseInStart(Transaction txn, + private IntroducerSession onRemoteResponseWhenDeclined(Transaction txn, IntroducerSession s, AbstractIntroductionMessage m) throws DbException { // The timestamp must be higher than the last request message @@ -355,33 +348,19 @@ class IntroducerProtocolEngine messageTracker .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); - Introducee i = getIntroducee(s, m.getGroupId()); + boolean senderIsAlice = senderIsAlice(s, m); Introducee introduceeA, introduceeB; - AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId(); - Contact c; - if (i.equals(s.getIntroduceeA())) { + if (senderIsAlice) { introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId()); introduceeB = s.getIntroduceeB(); - c = contactManager - .getContact(txn, s.getIntroduceeA().author.getId(), - localAuthorId); - } else if (i.equals(s.getIntroduceeB())) { + } else { introduceeA = s.getIntroduceeA(); introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId()); - c = contactManager - .getContact(txn, s.getIntroduceeB().author.getId(), - localAuthorId); - } else throw new AssertionError(); + } // Broadcast IntroductionResponseReceivedEvent - IntroductionResponse request = - new IntroductionResponse(s.getSessionId(), m.getMessageId(), - m.getGroupId(), INTRODUCER, m.getTimestamp(), false, - false, false, false, c.getAuthor().getName(), - m instanceof AcceptMessage); - IntroductionResponseReceivedEvent e = - new IntroductionResponseReceivedEvent(c.getId(), request); - txn.attach(e); + Author sender = senderIsAlice ? introduceeA.author : introduceeB.author; + broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m); return new IntroducerSession(s.getSessionId(), START, s.getRequestTimestamp(), introduceeA, introduceeB); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java index 99c3fbf86..6514eca16 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerState.java @@ -12,10 +12,11 @@ enum IntroducerState implements State { START(0), AWAIT_RESPONSES(1), AWAIT_RESPONSE_A(2), AWAIT_RESPONSE_B(3), - AWAIT_AUTHS(4), - AWAIT_AUTH_A(5), AWAIT_AUTH_B(6), - AWAIT_ACTIVATES(7), - AWAIT_ACTIVATE_A(8), AWAIT_ACTIVATE_B(9); + A_DECLINED(4), B_DECLINED(5), + AWAIT_AUTHS(6), + AWAIT_AUTH_A(7), AWAIT_AUTH_B(8), + AWAIT_ACTIVATES(9), + AWAIT_ACTIVATE_A(10), AWAIT_ACTIVATE_B(11); private final int value; diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 3dccbbc8d..3c602a2e3 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -20,7 +20,6 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportPropertyManager; 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.test.TestDatabaseModule; @@ -52,6 +51,10 @@ import static org.briarproject.bramble.test.TestUtils.getTransportProperties; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION; +import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_DECLINED; +import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED; +import static org.briarproject.briar.introduction.IntroducerState.B_DECLINED; +import static org.briarproject.briar.introduction.IntroducerState.START; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_A; @@ -171,8 +174,7 @@ public class IntroductionIntegrationTest sync0To2(1, true); // assert that introducee2 did add the transport keys - IntroduceeSession session2 = getIntroduceeSession(c2.getClientHelper(), - introductionManager2.getContactGroup(contact0From2).getId()); + IntroduceeSession session2 = getIntroduceeSession(c2); assertNotNull(session2.getTransportKeys()); assertFalse(session2.getTransportKeys().isEmpty()); @@ -181,8 +183,7 @@ public class IntroductionIntegrationTest sync0To1(2, true); // assert that introducee1 really purged the key material - IntroduceeSession session1 = getIntroduceeSession(c1.getClientHelper(), - introductionManager1.getContactGroup(contact0From1).getId()); + IntroduceeSession session1 = getIntroduceeSession(c1); assertNull(session1.getMasterKey()); assertNull(session1.getLocal().ephemeralPrivateKey); assertNull(session1.getTransportKeys()); @@ -240,16 +241,32 @@ public class IntroductionIntegrationTest assertTrue(listener1.requestReceived); assertTrue(listener2.requestReceived); + // assert that introducee is in correct state + IntroduceeSession introduceeSession = getIntroduceeSession(c1); + assertEquals(LOCAL_DECLINED, introduceeSession.getState()); + // sync first response sync1To0(1, true); eventWaiter.await(TIMEOUT, 1); assertTrue(listener0.response1Received); + // assert that introducer is in correct state + boolean alice = c0.getIntroductionCrypto() + .isAlice(introducee1.getAuthor().getId(), + introducee2.getAuthor().getId()); + IntroducerSession introducerSession = getIntroducerSession(); + assertEquals(alice ? A_DECLINED : B_DECLINED, + introducerSession.getState()); + // sync second response sync2To0(1, true); eventWaiter.await(TIMEOUT, 1); assertTrue(listener0.response2Received); + // assert that introducer now moved to START state + introducerSession = getIntroducerSession(); + assertEquals(START, introducerSession.getState()); + // sync first forwarded response sync0To2(1, true); @@ -1114,13 +1131,17 @@ public class IntroductionIntegrationTest return c0.getSessionParser().parseIntroducerSession(d); } - private IntroduceeSession getIntroduceeSession(ClientHelper ch, - GroupId introducerGroup) throws DbException, FormatException { - Map dicts = - ch.getMessageMetadataAsDictionary(getLocalGroup().getId()); + private IntroduceeSession getIntroduceeSession( + IntroductionIntegrationTestComponent c) + throws DbException, FormatException { + Map dicts = c.getClientHelper() + .getMessageMetadataAsDictionary(getLocalGroup().getId()); assertEquals(1, dicts.size()); BdfDictionary d = dicts.values().iterator().next(); - return c0.getSessionParser().parseIntroduceeSession(introducerGroup, d); + Group introducerGroup = + introductionManager2.getContactGroup(contact0From2); + return c.getSessionParser() + .parseIntroduceeSession(introducerGroup.getId(), d); } private Group getLocalGroup() { diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java index b8d5dedaa..afd6d4394 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java @@ -62,5 +62,6 @@ interface IntroductionIntegrationTestComponent MessageEncoder getMessageEncoder(); MessageParser getMessageParser(); SessionParser getSessionParser(); + IntroductionCrypto getIntroductionCrypto(); } From 337f7e7b8ff0f28f61e8ec4a43b0da770ff054a7 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 26 Apr 2018 18:18:31 -0300 Subject: [PATCH 18/21] Unify introduction response methods and handle ProtocolStateException It is possible that a remote DECLINE message arrives short before the user responds to the introduction. This will cause a ProtocolStateException which (for now) is just caught and a generic (existing) error message will be shown. --- .../android/contact/ConversationActivity.java | 16 ++++++----- .../api/introduction/IntroductionManager.java | 12 +++------ .../introduction/IntroductionManagerImpl.java | 13 +-------- .../IntroductionIntegrationTest.java | 27 ++++++------------- 4 files changed, 22 insertions(+), 46 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java index 585b0fed9..2af7fb8e5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java @@ -865,7 +865,8 @@ introductionOnboardingSeen(); "Unknown Request Type"); } loadMessages(); - } catch (DbException | FormatException e) { + } catch (DbException e) { + // TODO use more generic error message introductionResponseError(); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -898,11 +899,14 @@ introductionOnboardingSeen(); @DatabaseExecutor private void respondToIntroductionRequest(SessionId sessionId, - boolean accept, long time) throws DbException, FormatException { - if (accept) { - introductionManager.acceptIntroduction(contactId, sessionId, time); - } else { - introductionManager.declineIntroduction(contactId, sessionId, time); + boolean accept, long time) throws DbException { + try { + introductionManager + .respondToIntroduction(contactId, sessionId, time, accept); + } catch (ProtocolStateException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + introductionResponseError(); } } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java index 813b039de..8711193f1 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java @@ -34,16 +34,10 @@ public interface IntroductionManager extends ConversationClient { long timestamp) throws DbException; /** - * Accepts an introduction. + * Responds to an introduction. */ - void acceptIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException; - - /** - * Declines an introduction. - */ - void declineIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException; + void respondToIntroduction(ContactId contactId, SessionId sessionId, + long timestamp, boolean accept) throws DbException; /** * Returns all introduction messages for the given contact. diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index d14bd46c7..4082b8dcc 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -339,18 +339,7 @@ class IntroductionManagerImpl extends ConversationClientImpl } @Override - public void acceptIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException { - respondToRequest(contactId, sessionId, timestamp, true); - } - - @Override - public void declineIntroduction(ContactId contactId, SessionId sessionId, - long timestamp) throws DbException { - respondToRequest(contactId, sessionId, timestamp, false); - } - - private void respondToRequest(ContactId contactId, SessionId sessionId, + public void respondToIntroduction(ContactId contactId, SessionId sessionId, long timestamp, boolean accept) throws DbException { Transaction txn = db.startTransaction(false); try { diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 3c602a2e3..ab91ef92c 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -445,7 +445,8 @@ public class IntroductionIntegrationTest // answer request manually introductionManager2 - .acceptIntroduction(contactId0From2, listener2.sessionId, time); + .respondToIntroduction(contactId0From2, listener2.sessionId, time, + true); // sync second response and ACK and make sure there is no abort sync2To0(2, true); @@ -1003,25 +1004,13 @@ public class IntroductionIntegrationTest long time = clock.currentTimeMillis(); try { if (introducee == 1 && answerRequests) { - if (accept) { - introductionManager1 - .acceptIntroduction(contactId, sessionId, - time); - } else { - introductionManager1 - .declineIntroduction(contactId, sessionId, - time); - } + introductionManager1 + .respondToIntroduction(contactId, sessionId, + time, accept); } else if (introducee == 2 && answerRequests) { - if (accept) { - introductionManager2 - .acceptIntroduction(contactId, sessionId, - time); - } else { - introductionManager2 - .declineIntroduction(contactId, sessionId, - time); - } + introductionManager2 + .respondToIntroduction(contactId, sessionId, + time, accept); } } catch (DbException exception) { eventWaiter.rethrow(exception); From 80a96893163fb18543f79011e998c97b10263465 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 26 Apr 2018 19:20:10 -0300 Subject: [PATCH 19/21] Address second round of review comments --- .../briar/client/BdfIncomingMessageHook.java | 6 +- .../IntroduceeProtocolEngine.java | 12 +- .../IntroducerProtocolEngine.java | 6 +- .../introduction/IntroductionCrypto.java | 8 +- .../introduction/IntroductionCryptoImpl.java | 89 ++++++--------- .../introduction/IntroductionManagerImpl.java | 82 +++++++------- .../introduction/IntroductionValidator.java | 15 +-- .../briar/introduction/MessageEncoder.java | 3 +- .../introduction/MessageEncoderImpl.java | 7 +- ...=> IntroductionCryptoIntegrationTest.java} | 103 +++++++++--------- .../introduction/IntroductionCryptoTest.java | 5 +- .../IntroductionIntegrationTest.java | 6 +- .../IntroductionIntegrationTestComponent.java | 4 + .../IntroductionValidatorTest.java | 16 ++- .../MessageEncoderParserIntegrationTest.java | 10 +- .../introduction/MessageEncoderTest.java | 11 +- .../SessionEncoderParserIntegrationTest.java | 6 +- .../test/BriarIntegrationTestComponent.java | 9 +- .../briar/test/BriarTestUtils.java | 11 ++ 19 files changed, 196 insertions(+), 213 deletions(-) rename briar-core/src/test/java/org/briarproject/briar/introduction/{IntroductionCryptoImplTest.java => IntroductionCryptoIntegrationTest.java} (56%) diff --git a/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java b/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java index bf83fb6a2..edc62948d 100644 --- a/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java +++ b/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java @@ -16,8 +16,6 @@ import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook; import javax.annotation.concurrent.Immutable; -import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; - @Immutable @NotNullByDefault public abstract class BdfIncomingMessageHook implements IncomingMessageHook { @@ -57,9 +55,7 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook { public boolean incomingMessage(Transaction txn, Message m, Metadata meta) throws DbException, InvalidMessageException { try { - byte[] raw = m.getRaw(); - BdfList body = clientHelper.toList(raw, MESSAGE_HEADER_LENGTH, - raw.length - MESSAGE_HEADER_LENGTH); + BdfList body = clientHelper.toList(m); BdfDictionary metaDictionary = metadataParser.parse(meta); return incomingMessage(txn, m, body, metaDictionary); } catch (FormatException e) { diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index df0a71900..43f243a01 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -33,11 +33,13 @@ import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent; import java.security.GeneralSecurityException; import java.util.Map; +import java.util.logging.Logger; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static java.util.logging.Level.WARNING; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES; @@ -51,6 +53,9 @@ import static org.briarproject.briar.introduction.IntroduceeState.START; class IntroduceeProtocolEngine extends AbstractProtocolEngine { + private final static Logger LOG = + Logger.getLogger(IntroduceeProtocolEngine.class.getSimpleName()); + private final IntroductionCrypto crypto; private final KeyManager keyManager; private final TransportPropertyManager transportPropertyManager; @@ -383,11 +388,12 @@ class IntroduceeProtocolEngine bobMacKey = crypto.deriveMacKey(masterKey, false); SecretKey ourMacKey = s.getLocal().alice ? aliceMacKey : bobMacKey; LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); - mac = crypto.authMac(ourMacKey, s, localAuthor.getId(), - s.getLocal().alice); + mac = crypto.authMac(ourMacKey, s, localAuthor.getId()); signature = crypto.sign(ourMacKey, localAuthor.getPrivateKey()); } catch (GeneralSecurityException e) { // TODO + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); return abort(txn, s); } if (s.getState() != AWAIT_AUTH) throw new AssertionError(); @@ -406,7 +412,7 @@ class IntroduceeProtocolEngine LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); try { crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId()); - crypto.verifySignature(m.getSignature(), s, localAuthor.getId()); + crypto.verifySignature(m.getSignature(), s); } catch (GeneralSecurityException e) { return abort(txn, s); } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java index 8aeb28277..b923baac8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java @@ -207,8 +207,10 @@ class IntroducerProtocolEngine IntroducerSession s, @Nullable String message, long timestamp) throws DbException { // Send REQUEST messages - long localTimestamp = - Math.max(timestamp, getLocalTimestamp(s, s.getIntroduceeA())); + long maxIntroduceeTimestamp = + Math.max(getLocalTimestamp(s, s.getIntroduceeA()), + getLocalTimestamp(s, s.getIntroduceeB())); + long localTimestamp = Math.max(timestamp, maxIntroduceeTimestamp); Message sentA = sendRequestMessage(txn, s.getIntroduceeA(), localTimestamp, s.getIntroduceeB().author, message ); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java index 7504270d9..cc5c9ab1a 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java @@ -49,7 +49,7 @@ interface IntroductionCrypto { * transport properties, Author IDs and timestamps of the accept message. */ byte[] authMac(SecretKey macKey, IntroduceeSession s, - AuthorId localAuthorId, boolean alice); + AuthorId localAuthorId); /** * Verifies a received MAC @@ -74,12 +74,12 @@ interface IntroductionCrypto { throws GeneralSecurityException; /** - * Verifies the signature on a corresponding MAC key. + * Verifies the signature on a nonce derived from the MAC key. * * @throws GeneralSecurityException if the signature is invalid */ - void verifySignature(byte[] signature, IntroduceeSession s, - AuthorId localAuthorId) throws GeneralSecurityException; + void verifySignature(byte[] signature, IntroduceeSession s) + throws GeneralSecurityException; /** * Generates a MAC using the local MAC key. diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java index 6712a3153..db24fda6b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java @@ -13,12 +13,11 @@ 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.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.introduction.IntroduceeSession.Common; +import org.briarproject.briar.introduction.IntroduceeSession.Remote; import java.security.GeneralSecurityException; -import java.util.Map; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; @@ -32,6 +31,7 @@ import static org.briarproject.briar.api.introduction.IntroductionConstants.LABE 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.CLIENT_VERSION; +import static org.briarproject.briar.introduction.IntroduceeSession.Local; @Immutable @NotNullByDefault @@ -114,27 +114,16 @@ class IntroductionCryptoImpl implements IntroductionCrypto { @Override @SuppressWarnings("ConstantConditions") public byte[] authMac(SecretKey macKey, IntroduceeSession s, - AuthorId localAuthorId, boolean alice) { + AuthorId localAuthorId) { + // the macKey is not yet available in the session at this point return authMac(macKey, s.getIntroducer().getId(), localAuthorId, - s.getRemote().author.getId(), s.getLocal().acceptTimestamp, - s.getRemote().acceptTimestamp, s.getLocal().ephemeralPublicKey, - s.getRemote().ephemeralPublicKey, - s.getLocal().transportProperties, - s.getRemote().transportProperties, alice); + s.getLocal(), s.getRemote()); } byte[] authMac(SecretKey macKey, AuthorId introducerId, - AuthorId localAuthorId, AuthorId remoteAuthorId, - long acceptTimestamp, long remoteAcceptTimestamp, - byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey, - Map transportProperties, - Map remoteTransportProperties, - boolean alice) { - byte[] inputs = - getAuthMacInputs(introducerId, localAuthorId, remoteAuthorId, - acceptTimestamp, remoteAcceptTimestamp, - ephemeralPublicKey, remoteEphemeralPublicKey, - transportProperties, remoteTransportProperties, alice); + AuthorId localAuthorId, Local local, Remote remote) { + byte[] inputs = getAuthMacInputs(introducerId, localAuthorId, local, + remote.author.getId(), remote); return crypto.mac( LABEL_AUTH_MAC, macKey, @@ -145,59 +134,43 @@ class IntroductionCryptoImpl implements IntroductionCrypto { @Override @SuppressWarnings("ConstantConditions") public void verifyAuthMac(byte[] mac, IntroduceeSession s, - AuthorId localAuthorId) - throws GeneralSecurityException { - boolean alice = isAlice(localAuthorId, s.getRemote().author.getId()); + AuthorId localAuthorId) throws GeneralSecurityException { verifyAuthMac(mac, new SecretKey(s.getRemote().macKey), - s.getIntroducer().getId(), localAuthorId, - s.getRemote().author.getId(), s.getLocal().acceptTimestamp, - s.getRemote().acceptTimestamp, s.getLocal().ephemeralPublicKey, - s.getRemote().ephemeralPublicKey, - s.getLocal().transportProperties, - s.getRemote().transportProperties, !alice); + s.getIntroducer().getId(), localAuthorId, s.getLocal(), + s.getRemote().author.getId(), s.getRemote()); } - void verifyAuthMac(byte[] mac, SecretKey macKey, - AuthorId introducerId, AuthorId localAuthorId, - AuthorId remoteAuthorId, long acceptTimestamp, - long remoteAcceptTimestamp, byte[] ephemeralPublicKey, - byte[] remoteEphemeralPublicKey, - Map transportProperties, - Map remoteTransportProperties, - boolean alice) throws GeneralSecurityException { - byte[] inputs = - getAuthMacInputs(introducerId, localAuthorId, remoteAuthorId, - acceptTimestamp, remoteAcceptTimestamp, - ephemeralPublicKey, remoteEphemeralPublicKey, - transportProperties, remoteTransportProperties, !alice); + 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, AuthorId remoteAuthorId, - long acceptTimestamp, long remoteAcceptTimestamp, - byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey, - Map transportProperties, - Map remoteTransportProperties, - boolean alice) { + AuthorId localAuthorId, Common local, AuthorId remoteAuthorId, + Common remote) { BdfList localInfo = BdfList.of( localAuthorId, - acceptTimestamp, - ephemeralPublicKey, - clientHelper.toDictionary(transportProperties) + local.acceptTimestamp, + local.ephemeralPublicKey, + clientHelper.toDictionary(local.transportProperties) ); BdfList remoteInfo = BdfList.of( remoteAuthorId, - remoteAcceptTimestamp, - remoteEphemeralPublicKey, - clientHelper.toDictionary(remoteTransportProperties) + remote.acceptTimestamp, + remote.ephemeralPublicKey, + clientHelper.toDictionary(remote.transportProperties) ); BdfList macList = BdfList.of( introducerId, - alice ? localInfo : remoteInfo, - alice ? remoteInfo : localInfo + localInfo, + remoteInfo ); try { return clientHelper.toByteArray(macList); @@ -218,8 +191,8 @@ class IntroductionCryptoImpl implements IntroductionCrypto { @Override @SuppressWarnings("ConstantConditions") - public void verifySignature(byte[] signature, IntroduceeSession s, - AuthorId localAuthorId) throws GeneralSecurityException { + public void verifySignature(byte[] signature, IntroduceeSession s) + throws GeneralSecurityException { SecretKey macKey = new SecretKey(s.getRemote().macKey); verifySignature(macKey, s.getRemote().author.getPublicKey(), signature); } diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index 4082b8dcc..89d26c73f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -71,6 +71,8 @@ class IntroductionManagerImpl extends ConversationClientImpl private final IntroductionCrypto crypto; private final IdentityManager identityManager; + private final Group localGroup; + @Inject IntroductionManagerImpl( DatabaseComponent db, @@ -96,12 +98,13 @@ class IntroductionManagerImpl extends ConversationClientImpl this.introduceeEngine = introduceeEngine; this.crypto = crypto; this.identityManager = identityManager; + this.localGroup = + contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); } @Override public void createLocalState(Transaction txn) throws DbException { // Create a local group to store protocol sessions - Group localGroup = getLocalGroup(); if (db.containsGroup(txn, localGroup.getId())) return; db.addGroup(txn, localGroup); // Set up groups for communication with any pre-existing contacts @@ -229,8 +232,7 @@ class IntroductionManagerImpl extends ConversationClientImpl if (sessionId == null) return null; BdfDictionary query = sessionParser.getSessionQuery(sessionId); Map results = clientHelper - .getMessageMetadataAsDictionary(txn, getLocalGroup().getId(), - query); + .getMessageMetadataAsDictionary(txn, localGroup.getId(), query); if (results.size() > 1) throw new DbException(); if (results.isEmpty()) return null; return new StoredSession(results.keySet().iterator().next(), @@ -246,7 +248,7 @@ class IntroductionManagerImpl extends ConversationClientImpl private MessageId createStorageId(Transaction txn) throws DbException { Message m = clientHelper - .createMessageForStoringMetadata(getLocalGroup().getId()); + .createMessageForStoringMetadata(localGroup.getId()); db.addLocalMessage(txn, m, new Metadata(), false); return m.getId(); } @@ -274,22 +276,28 @@ class IntroductionManagerImpl extends ConversationClientImpl public boolean canIntroduce(Contact c1, Contact c2) throws DbException { Transaction txn = db.startTransaction(true); try { - // Look up the session, if there is one - Author introducer = identityManager.getLocalAuthor(txn); - SessionId sessionId = - crypto.getSessionId(introducer, c1.getAuthor(), - c2.getAuthor()); - StoredSession ss = getSession(txn, sessionId); - if (ss == null) return true; - IntroducerSession session = - sessionParser.parseIntroducerSession(ss.bdfSession); - if (session.getState() == START) return true; + boolean can = canIntroduce(txn, c1, c2); + db.commitTransaction(txn); + return can; } catch (FormatException e) { throw new DbException(e); } finally { db.endTransaction(txn); } - return false; + } + + private boolean canIntroduce(Transaction txn, Contact c1, Contact c2) + throws DbException, FormatException { + // Look up the session, if there is one + Author introducer = identityManager.getLocalAuthor(txn); + SessionId sessionId = + crypto.getSessionId(introducer, c1.getAuthor(), + c2.getAuthor()); + StoredSession ss = getSession(txn, sessionId); + if (ss == null) return true; + IntroducerSession session = + sessionParser.parseIntroducerSession(ss.bdfSession); + return session.getState() == START; } @Override @@ -395,12 +403,12 @@ class IntroductionManagerImpl extends ConversationClientImpl meta, status, ss.bdfSession)); } else if (type == ACCEPT) { messages.add( - parseInvitationResponse(txn, contactGroupId, m, - meta, status, ss.bdfSession, true)); + parseInvitationResponse(contactGroupId, m, meta, + status, ss.bdfSession, true)); } else if (type == DECLINE) { messages.add( - parseInvitationResponse(txn, contactGroupId, m, - meta, status, ss.bdfSession, false)); + parseInvitationResponse(contactGroupId, m, meta, + status, ss.bdfSession, false)); } } db.commitTransaction(txn); @@ -435,8 +443,8 @@ class IntroductionManagerImpl extends ConversationClientImpl author = session.getRemote().author; } else throw new AssertionError(); Message msg = clientHelper.getMessage(txn, m); - BdfList body = clientHelper.getMessageAsList(txn, m); - if (msg == null || body == null) throw new AssertionError(); + if (msg == null) throw new AssertionError(); + BdfList body = clientHelper.toList(msg); RequestMessage rm = messageParser.parseRequestMessage(msg, body); String message = rm.getMessage(); LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); @@ -451,10 +459,9 @@ class IntroductionManagerImpl extends ConversationClientImpl contactExists); } - private IntroductionResponse parseInvitationResponse(Transaction txn, - GroupId contactGroupId, MessageId m, MessageMetadata meta, - MessageStatus status, BdfDictionary bdfSession, boolean accept) - throws FormatException, DbException { + private IntroductionResponse parseInvitationResponse(GroupId contactGroupId, + MessageId m, MessageMetadata meta, MessageStatus status, + BdfDictionary bdfSession, boolean accept) throws FormatException { Role role = sessionParser.getRole(bdfSession); SessionId sessionId; Author author; @@ -462,8 +469,7 @@ class IntroductionManagerImpl extends ConversationClientImpl IntroducerSession session = sessionParser.parseIntroducerSession(bdfSession); sessionId = session.getSessionId(); - LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); - if (localAuthor.equals(session.getIntroduceeA().author)) { + if (contactGroupId.equals(session.getIntroduceeA().groupId)) { author = session.getIntroduceeB().author; } else { author = session.getIntroduceeA().author; @@ -485,13 +491,13 @@ class IntroductionManagerImpl extends ConversationClientImpl .getIntroduceeSessionsByIntroducerQuery(introducer.getAuthor()); Map sessions; try { - sessions = clientHelper.getMessageMetadataAsDictionary(txn, - getLocalGroup().getId(), query); + sessions = clientHelper + .getMessageMetadataAsDictionary(txn, localGroup.getId(), + query); } catch (FormatException e) { - throw new AssertionError(e); + throw new DbException(e); } for (MessageId id : sessions.keySet()) { - db.deleteMessageMetadata(txn, id); // TODO needed? db.removeMessage(txn, id); } } @@ -501,10 +507,11 @@ class IntroductionManagerImpl extends ConversationClientImpl BdfDictionary query = sessionEncoder.getIntroducerSessionsQuery(); Map sessions; try { - sessions = clientHelper.getMessageMetadataAsDictionary(txn, - getLocalGroup().getId(), query); + sessions = clientHelper + .getMessageMetadataAsDictionary(txn, localGroup.getId(), + query); } catch (FormatException e) { - throw new AssertionError(); + throw new DbException(); } LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); for (Entry session : sessions.entrySet()) { @@ -512,7 +519,7 @@ class IntroductionManagerImpl extends ConversationClientImpl try { s = sessionParser.parseIntroducerSession(session.getValue()); } catch (FormatException e) { - throw new AssertionError(); + throw new DbException(); } if (s.getIntroduceeA().author.equals(c.getAuthor())) { abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(), @@ -531,15 +538,10 @@ class IntroductionManagerImpl extends ConversationClientImpl IntroducerSession session = introducerEngine.onAbortAction(txn, s); storeSession(txn, storageId, session); } else { - db.deleteMessageMetadata(txn, storageId); // TODO needed? db.removeMessage(txn, storageId); } } - private Group getLocalGroup() { - return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION); - } - private static class StoredSession { private final MessageId storageId; diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java index 6b59a64ae..362d7e247 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java @@ -22,7 +22,6 @@ 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.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; @@ -79,8 +78,8 @@ class IntroductionValidator extends BdfMessageValidator { String msg = body.getOptionalString(3); checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH); - BdfDictionary meta = messageEncoder - .encodeRequestMetadata(m.getTimestamp(), false, false, false); + BdfDictionary meta = + messageEncoder.encodeRequestMetadata(m.getTimestamp()); if (previousMessageId == null) { return new BdfMessageContext(meta); } else { @@ -103,15 +102,13 @@ class IntroductionValidator extends BdfMessageValidator { byte[] ephemeralPublicKey = body.getRaw(3); checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH); - body.getLong(4); + long timestamp = body.getLong(4); + if (timestamp < 0) throw new FormatException(); BdfDictionary transportProperties = body.getDictionary(5); if (transportProperties.size() < 1) throw new FormatException(); - for (String tId : transportProperties.keySet()) { - checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH); - BdfDictionary tProps = transportProperties.getDictionary(tId); - clientHelper.parseAndValidateTransportProperties(tProps); - } + clientHelper + .parseAndValidateTransportPropertiesMap(transportProperties); SessionId sessionId = new SessionId(sessionIdBytes); BdfDictionary meta = messageEncoder diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java index 2eed3057a..1327b54a9 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java @@ -17,8 +17,7 @@ import javax.annotation.Nullable; @NotNullByDefault interface MessageEncoder { - BdfDictionary encodeRequestMetadata(long timestamp, boolean local, - boolean read, boolean available); + BdfDictionary encodeRequestMetadata(long timestamp); BdfDictionary encodeMetadata(MessageType type, @Nullable SessionId sessionId, long timestamp, boolean local, diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java index 89d3e40e5..fb3d66f38 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java @@ -47,11 +47,10 @@ class MessageEncoderImpl implements MessageEncoder { } @Override - public BdfDictionary encodeRequestMetadata(long timestamp, - boolean local, boolean read, boolean available) { + public BdfDictionary encodeRequestMetadata(long timestamp) { BdfDictionary meta = - encodeMetadata(REQUEST, null, timestamp, local, read, false); - meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available); + encodeMetadata(REQUEST, null, timestamp, false, false, false); + meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false); return meta; } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoIntegrationTest.java similarity index 56% rename from briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java rename to briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoIntegrationTest.java index 59cf3b4f4..81c2ae01c 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoIntegrationTest.java @@ -11,23 +11,25 @@ import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.test.BriarIntegrationTestComponent; -import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent; import org.junit.Test; import java.util.Map; import javax.inject.Inject; -import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; -import static org.briarproject.bramble.util.StringUtils.fromHexString; +import static org.briarproject.briar.introduction.IntroduceeSession.Local; +import static org.briarproject.briar.introduction.IntroduceeSession.Remote; +import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor; +import static org.briarproject.briar.test.BriarTestUtils.getRealLocalAuthor; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; -public class IntroductionCryptoImplTest extends BrambleTestCase { +public class IntroductionCryptoIntegrationTest extends BrambleTestCase { @Inject ClientHelper clientHelper; @@ -42,33 +44,28 @@ public class IntroductionCryptoImplTest extends BrambleTestCase { private final LocalAuthor alice, bob; private final long aliceAcceptTimestamp = 42L; private final long bobAcceptTimestamp = 1337L; - private final SecretKey masterKey = - new SecretKey(getRandomBytes(SecretKey.LENGTH)); + private final SecretKey masterKey = getSecretKey(); private final KeyPair aliceEphemeral, bobEphemeral; private final Map aliceTransport = getTransportPropertiesMap(3); private final Map bobTransport = getTransportPropertiesMap(3); - public IntroductionCryptoImplTest() { - BriarIntegrationTestComponent component = - DaggerBriarIntegrationTestComponent.builder().build(); + public IntroductionCryptoIntegrationTest() { + IntroductionIntegrationTestComponent component = + DaggerIntroductionIntegrationTestComponent.builder().build(); component.inject(this); crypto = new IntroductionCryptoImpl(cryptoComponent, clientHelper); - // create actual deterministic authors for testing - introducer = authorFactory - .createAuthor("Introducer", new byte[] {0x1, 0x2, 0x3}); - alice = authorFactory.createLocalAuthor("Alice", - fromHexString( - "A626F080C94771698F86B4B4094C4F560904B53398805AE02BA2343F1829187A"), - fromHexString( - "60F010187AF91ACA15141E8C811EC8E79C7CAA6461C21A852BB03066C89B0A70")); - bob = authorFactory.createLocalAuthor("Bob", - fromHexString( - "A0D0FED1CE4674D8B6441AD0A664E41BF60D489F35DA11F52AF923540848546F"), - fromHexString( - "20B25BE7E999F68FE07189449E91984FA79121DBFF28A651669A3CF512D6A758")); + introducer = getRealAuthor(authorFactory); + LocalAuthor introducee1 = + getRealLocalAuthor(cryptoComponent, authorFactory); + LocalAuthor introducee2 = + getRealLocalAuthor(cryptoComponent, authorFactory); + boolean isAlice = + crypto.isAlice(introducee1.getId(), introducee2.getId()); + alice = isAlice ? introducee1 : introducee2; + bob = isAlice ? introducee2 : introducee1; aliceEphemeral = crypto.generateKeyPair(); bobEphemeral = crypto.generateKeyPair(); } @@ -78,6 +75,9 @@ public class IntroductionCryptoImplTest extends BrambleTestCase { SessionId s1 = crypto.getSessionId(introducer, alice, bob); SessionId s2 = crypto.getSessionId(introducer, bob, alice); assertEquals(s1, s2); + + SessionId s3 = crypto.getSessionId(alice, bob, introducer); + assertNotEquals(s1, s3); } @Test @@ -88,62 +88,66 @@ public class IntroductionCryptoImplTest extends BrambleTestCase { @Test public void testDeriveMasterKey() throws Exception { - SecretKey aliceMasterKey = crypto.deriveMasterKey(alice.getPublicKey(), - alice.getPrivateKey(), bob.getPublicKey(), true); - SecretKey bobMasterKey = crypto.deriveMasterKey(bob.getPublicKey(), - bob.getPrivateKey(), alice.getPublicKey(), false); + SecretKey aliceMasterKey = + crypto.deriveMasterKey(aliceEphemeral.getPublic().getEncoded(), + aliceEphemeral.getPrivate().getEncoded(), + bobEphemeral.getPublic().getEncoded(), true); + SecretKey bobMasterKey = + crypto.deriveMasterKey(bobEphemeral.getPublic().getEncoded(), + bobEphemeral.getPrivate().getEncoded(), + aliceEphemeral.getPublic().getEncoded(), false); assertArrayEquals(aliceMasterKey.getBytes(), bobMasterKey.getBytes()); } @Test public void testAliceAuthMac() throws Exception { SecretKey aliceMacKey = crypto.deriveMacKey(masterKey, true); + Local local = new Local(true, null, -1, + aliceEphemeral.getPublic().getEncoded(), + aliceEphemeral.getPrivate().getEncoded(), aliceTransport, + aliceAcceptTimestamp, aliceMacKey.getBytes()); + Remote remote = new Remote(false, bob, null, + bobEphemeral.getPublic().getEncoded(), bobTransport, + bobAcceptTimestamp, null); byte[] aliceMac = crypto.authMac(aliceMacKey, introducer.getId(), alice.getId(), - bob.getId(), aliceAcceptTimestamp, bobAcceptTimestamp, - aliceEphemeral.getPublic().getEncoded(), - bobEphemeral.getPublic().getEncoded(), aliceTransport, - bobTransport, true); + local, remote); + // verify from Bob's perspective crypto.verifyAuthMac(aliceMac, aliceMacKey, introducer.getId(), - bob.getId(), alice.getId(), bobAcceptTimestamp, - aliceAcceptTimestamp, bobEphemeral.getPublic().getEncoded(), - aliceEphemeral.getPublic().getEncoded(), bobTransport, - aliceTransport, true); + bob.getId(), remote, alice.getId(), local); } @Test public void testBobAuthMac() throws Exception { SecretKey bobMacKey = crypto.deriveMacKey(masterKey, false); + Local local = new Local(false, null, -1, + bobEphemeral.getPublic().getEncoded(), + bobEphemeral.getPrivate().getEncoded(), bobTransport, + bobAcceptTimestamp, bobMacKey.getBytes()); + Remote remote = new Remote(true, alice, null, + aliceEphemeral.getPublic().getEncoded(), aliceTransport, + aliceAcceptTimestamp, null); byte[] bobMac = crypto.authMac(bobMacKey, introducer.getId(), bob.getId(), - alice.getId(), bobAcceptTimestamp, aliceAcceptTimestamp, - bobEphemeral.getPublic().getEncoded(), - aliceEphemeral.getPublic().getEncoded(), bobTransport, - aliceTransport, false); + local, remote); + // verify from Alice's perspective crypto.verifyAuthMac(bobMac, bobMacKey, introducer.getId(), - alice.getId(), bob.getId(), aliceAcceptTimestamp, - bobAcceptTimestamp, aliceEphemeral.getPublic().getEncoded(), - bobEphemeral.getPublic().getEncoded(), aliceTransport, - bobTransport, false); + alice.getId(), remote, bob.getId(), local); } @Test public void testSign() throws Exception { - KeyPair keyPair = cryptoComponent.generateSignatureKeyPair(); SecretKey macKey = crypto.deriveMacKey(masterKey, true); - byte[] signature = - crypto.sign(macKey, keyPair.getPrivate().getEncoded()); - crypto.verifySignature(macKey, keyPair.getPublic().getEncoded(), - signature); + byte[] signature = crypto.sign(macKey, alice.getPrivateKey()); + crypto.verifySignature(macKey, alice.getPublicKey(), signature); } @Test public void testAliceActivateMac() throws Exception { SecretKey aliceMacKey = crypto.deriveMacKey(masterKey, true); byte[] aliceMac = crypto.activateMac(aliceMacKey); - crypto.verifyActivateMac(aliceMac, aliceMacKey); } @@ -151,7 +155,6 @@ public class IntroductionCryptoImplTest extends BrambleTestCase { public void testBobActivateMac() throws Exception { SecretKey bobMacKey = crypto.deriveMacKey(masterKey, false); byte[] bobMac = crypto.activateMac(bobMacKey); - crypto.verifyActivateMac(bobMac, bobMacKey); } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java index a28e8321e..f966aa2cb 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java @@ -1,6 +1,5 @@ package org.briarproject.briar.introduction; -import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.identity.Author; @@ -10,7 +9,7 @@ import org.jmock.Expectations; import org.junit.Test; import static org.briarproject.bramble.test.TestUtils.getAuthor; -import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID; import static org.junit.Assert.assertEquals; @@ -25,7 +24,7 @@ public class IntroductionCryptoTest extends BrambleMockTestCase { private final Author introducer = getAuthor(); private final Author alice = getAuthor(), bob = getAuthor(); - private final byte[] hash = getRandomBytes(UniqueId.LENGTH); + private final byte[] hash = getRandomId(); @Test public void testGetSessionId() { diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index ab91ef92c..b692492f4 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -634,7 +634,7 @@ public class IntroductionIntegrationTest // fake a second ACCEPT message from introducee1 Message msg = c1.getMessageEncoder() - .encodeAcceptMessage(m.getGroupId(), clock.currentTimeMillis(), + .encodeAcceptMessage(m.getGroupId(), m.getTimestamp() + 1, m.getMessageId(), m.getSessionId(), m.getEphemeralPublicKey(), m.getAcceptTimestamp(), m.getTransportProperties()); @@ -671,7 +671,7 @@ public class IntroductionIntegrationTest // fake a second DECLINE message also from introducee1 Message msg = c1.getMessageEncoder() - .encodeDeclineMessage(m.getGroupId(), clock.currentTimeMillis(), + .encodeDeclineMessage(m.getGroupId(), m.getTimestamp() + 1, m.getMessageId(), m.getSessionId()); c1.getClientHelper().addLocalMessage(msg, new BdfDictionary(), true); @@ -715,7 +715,7 @@ public class IntroductionIntegrationTest // fake a second AUTH message also from introducee1 Message msg = c1.getMessageEncoder() - .encodeAuthMessage(m.getGroupId(), clock.currentTimeMillis(), + .encodeAuthMessage(m.getGroupId(), m.getTimestamp() + 1, m.getMessageId(), m.getSessionId(), m.getMac(), m.getSignature()); c1.getClientHelper().addLocalMessage(msg, new BdfDictionary(), true); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java index afd6d4394..3a90d7d14 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java @@ -59,6 +59,10 @@ interface IntroductionIntegrationTestComponent void inject(IntroductionIntegrationTest init); + void inject(MessageEncoderParserIntegrationTest init); + void inject(SessionEncoderParserIntegrationTest init); + void inject(IntroductionCryptoIntegrationTest init); + MessageEncoder getMessageEncoder(); MessageParser getMessageParser(); SessionParser getSessionParser(); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java index 7614121cf..b1ad9caa9 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java @@ -126,8 +126,8 @@ public class IntroductionValidatorTest extends ValidatorTestCase { previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp, transportProperties); context.checking(new Expectations() {{ - oneOf(clientHelper).parseAndValidateTransportProperties( - transportProperties.getDictionary("transportId")); + oneOf(clientHelper).parseAndValidateTransportPropertiesMap( + transportProperties); }}); expectEncodeMetadata(ACCEPT); BdfMessageContext messageContext = @@ -178,6 +178,14 @@ public class IntroductionValidatorTest extends ValidatorTestCase { validator.validateMessage(message, group, body); } + @Test(expected = FormatException.class) + public void testRejectsNegativeTimestampForAccept() throws Exception { + BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), + previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH), + -1, transportProperties); + validator.validateMessage(message, group, body); + } + @Test(expected = FormatException.class) public void testRejectsEmptyTransportPropertiesForAccept() throws Exception { @@ -405,9 +413,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase { private void expectEncodeRequestMetadata() { context.checking(new Expectations() {{ - oneOf(messageEncoder) - .encodeRequestMetadata(message.getTimestamp(), false, false, - false); + oneOf(messageEncoder).encodeRequestMetadata(message.getTimestamp()); will(returnValue(meta)); }}); } diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java index f3480f158..7b15b6ab4 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java @@ -16,8 +16,6 @@ import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.briar.api.client.SessionId; -import org.briarproject.briar.test.BriarIntegrationTestComponent; -import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent; import org.junit.Test; import java.util.Map; @@ -72,8 +70,8 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES); public MessageEncoderParserIntegrationTest() { - BriarIntegrationTestComponent component = - DaggerBriarIntegrationTestComponent.builder().build(); + IntroductionIntegrationTestComponent component = + DaggerIntroductionIntegrationTestComponent.builder().build(); component.inject(this); messageEncoder = new MessageEncoderImpl(clientHelper, messageFactory); @@ -86,13 +84,13 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase { @Test public void testRequestMessageMetadata() throws FormatException { BdfDictionary d = messageEncoder - .encodeRequestMetadata(timestamp, true, false, false); + .encodeRequestMetadata(timestamp); MessageMetadata meta = messageParser.parseMetadata(d); assertEquals(REQUEST, meta.getMessageType()); assertNull(meta.getSessionId()); assertEquals(timestamp, meta.getTimestamp()); - assertTrue(meta.isLocal()); + assertFalse(meta.isLocal()); assertFalse(meta.isRead()); assertFalse(meta.isVisibleInConversation()); assertFalse(meta.isAvailableToAnswer()); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java index b1dae81c8..56fe04bdb 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java @@ -7,13 +7,12 @@ import org.briarproject.bramble.api.identity.Author; 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.bramble.test.BrambleMockTestCase; import org.jmock.Expectations; import org.junit.Test; import static org.briarproject.bramble.test.TestUtils.getAuthor; -import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH; @@ -28,11 +27,9 @@ public class MessageEncoderTest extends BrambleMockTestCase { new MessageEncoderImpl(clientHelper, messageFactory); private final GroupId groupId = new GroupId(getRandomId()); - private final long timestamp = 42L; - private final Message message = - new Message(new MessageId(getRandomId()), groupId, timestamp, - getRandomBytes(48)); - private final byte[] body = getRandomBytes(42); + private final Message message = getMessage(groupId); + private final long timestamp = message.getTimestamp(); + private final byte[] body = message.getRaw(); private final Author author = getAuthor(); private final BdfList authorList = new BdfList(); private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java index ffbcaa943..9ddb00632 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java @@ -14,8 +14,6 @@ import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.introduction.IntroducerSession.Introducee; -import org.briarproject.briar.test.BriarIntegrationTestComponent; -import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent; import org.junit.Test; import java.util.HashMap; @@ -82,8 +80,8 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase { private final byte[] remoteMacKey = getRandomBytes(SecretKey.LENGTH); public SessionEncoderParserIntegrationTest() { - BriarIntegrationTestComponent component = - DaggerBriarIntegrationTestComponent.builder().build(); + IntroductionIntegrationTestComponent component = + DaggerIntroductionIntegrationTestComponent.builder().build(); component.inject(this); sessionEncoder = new SessionEncoderImpl(clientHelper); diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index f918b0a2d..ecaf60d1b 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -9,7 +9,6 @@ import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.sync.SyncSessionFactory; -import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.client.ClientModule; import org.briarproject.bramble.contact.ContactModule; import org.briarproject.bramble.crypto.CryptoModule; @@ -37,8 +36,8 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager import org.briarproject.briar.blog.BlogModule; import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.forum.ForumModule; +import org.briarproject.briar.introduction.IntroductionCryptoIntegrationTest; import org.briarproject.briar.introduction.IntroductionModule; -import org.briarproject.briar.introduction.IntroductionCryptoImplTest; import org.briarproject.briar.introduction.MessageEncoderParserIntegrationTest; import org.briarproject.briar.introduction.SessionEncoderParserIntegrationTest; import org.briarproject.briar.messaging.MessagingModule; @@ -80,10 +79,6 @@ public interface BriarIntegrationTestComponent { void inject(BriarIntegrationTest init); - void inject(MessageEncoderParserIntegrationTest init); - void inject(SessionEncoderParserIntegrationTest init); - void inject(IntroductionCryptoImplTest init); - void inject(BlogModule.EagerSingletons init); void inject(ContactModule.EagerSingletons init); @@ -146,8 +141,6 @@ public interface BriarIntegrationTestComponent { TransportPropertyManager getTransportPropertyManager(); - KeyManager getKeyManager(); - AuthorFactory getAuthorFactory(); BlogFactory getBlogFactory(); diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java index 5de39e9f5..9f71087eb 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java @@ -1,8 +1,11 @@ package org.briarproject.briar.test; +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.AuthorFactory; +import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker.GroupCount; @@ -35,4 +38,12 @@ public class BriarTestUtils { getRandomBytes(MAX_PUBLIC_KEY_LENGTH)); } + public static LocalAuthor getRealLocalAuthor( + CryptoComponent cryptoComponent, AuthorFactory authorFactory) { + KeyPair keyPair = cryptoComponent.generateSignatureKeyPair(); + return authorFactory.createLocalAuthor(getRandomString(5), + keyPair.getPublic().getEncoded(), + keyPair.getPrivate().getEncoded()); + } + } From 44f5a9db1ea581d56d758fb3e60a8f0924e338ae Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 27 Apr 2018 11:04:08 -0300 Subject: [PATCH 20/21] Address last review comments --- .../IntroduceeProtocolEngine.java | 1 - .../IntroducerProtocolEngine.java | 7 +++- .../introduction/IntroductionCrypto.java | 3 ++ .../introduction/IntroductionCryptoImpl.java | 5 +-- .../introduction/IntroductionManagerImpl.java | 7 +++- .../introduction/IntroductionValidator.java | 2 +- .../IntroductionIntegrationTest.java | 39 +++++++++++++++++++ .../IntroductionValidatorTest.java | 23 +++++++++++ 8 files changed, 79 insertions(+), 8 deletions(-) diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index 43f243a01..322e46a6b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -391,7 +391,6 @@ class IntroduceeProtocolEngine mac = crypto.authMac(ourMacKey, s, localAuthor.getId()); signature = crypto.sign(ourMacKey, localAuthor.getPrivateKey()); } catch (GeneralSecurityException e) { - // TODO if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); return abort(txn, s); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java index b923baac8..ed19fe651 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java @@ -343,6 +343,12 @@ class IntroducerProtocolEngine // The dependency, if any, must be the last remote message if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId())) return abort(txn, s); + // The message must be expected in the current state + boolean senderIsAlice = senderIsAlice(s, m); + if (senderIsAlice && s.getState() != B_DECLINED) + return abort(txn, s); + else if (!senderIsAlice && s.getState() != A_DECLINED) + return abort(txn, s); // Mark the response visible in the UI markMessageVisibleInUi(txn, m.getMessageId()); @@ -350,7 +356,6 @@ class IntroducerProtocolEngine messageTracker .trackMessage(txn, m.getGroupId(), m.getTimestamp(), false); - boolean senderIsAlice = senderIsAlice(s, m); Introducee introduceeA, introduceeB; if (senderIsAlice) { introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId()); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java index cc5c9ab1a..37f7aa10f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java @@ -19,6 +19,9 @@ interface IntroductionCrypto { /** * Returns true if the local author is alice + * + * Alice is the Author whose unique ID has the lower ID, + * comparing the IDs as byte strings. */ boolean isAlice(AuthorId local, AuthorId remote); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java index db24fda6b..f9d53323b 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java @@ -1,6 +1,5 @@ package org.briarproject.briar.introduction; -import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.crypto.CryptoComponent; @@ -68,9 +67,7 @@ class IntroductionCryptoImpl implements IntroductionCrypto { @Override public boolean isAlice(AuthorId local, AuthorId remote) { - byte[] a = local.getBytes(); - byte[] b = remote.getBytes(); - return Bytes.COMPARATOR.compare(new Bytes(a), new Bytes(b)) < 0; + return local.compareTo(remote) < 0; } @Override diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java index 89d26c73f..efb857a0f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java @@ -353,7 +353,12 @@ class IntroductionManagerImpl extends ConversationClientImpl try { // Look up the session StoredSession ss = getSession(txn, sessionId); - if (ss == null) throw new IllegalArgumentException(); + if (ss == null) { + // Actions from the UI may be based on stale information. + // The contact might just have been deleted, for example. + // Throwing a DbException here aborts gracefully. + throw new DbException(); + } // Parse the session Contact contact = db.getContact(txn, contactId); GroupId contactGroupId = getContactGroup(contact).getId(); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java index 362d7e247..929c8bddf 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java @@ -155,7 +155,7 @@ class IntroductionValidator extends BdfMessageValidator { byte[] sessionIdBytes = body.getRaw(1); checkLength(sessionIdBytes, UniqueId.LENGTH); - byte[] previousMessageId = body.getOptionalRaw(2); + byte[] previousMessageId = body.getRaw(2); checkLength(previousMessageId, UniqueId.LENGTH); byte[] mac = body.getOptionalRaw(3); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index b692492f4..df0d46b88 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -63,6 +63,7 @@ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID; import static org.briarproject.briar.introduction.MessageType.ACCEPT; import static org.briarproject.briar.introduction.MessageType.AUTH; +import static org.briarproject.briar.introduction.MessageType.DECLINE; import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -681,6 +682,41 @@ public class IntroductionIntegrationTest assertTrue(listener0.aborted); } + /** + * One introducee sends an DECLINE and then another DECLINE message. + * The introducer should notice this and ABORT the session. + */ + @Test + public void testDoubleDecline() throws Exception { + addListeners(false, true); + + // make the introduction + long time = clock.currentTimeMillis(); + introductionManager0 + .makeIntroduction(contact1From0, contact2From0, null, time); + + // sync REQUEST to introducee1 + sync0To1(1, true); + + // save DECLINE from introducee1 + DeclineMessage m = (DeclineMessage) getMessageFor(c1.getClientHelper(), + contact0From1, DECLINE); + + // sync DECLINE back to introducer + sync1To0(1, true); + + // fake a second DECLINE message also from introducee1 + Message msg = c1.getMessageEncoder() + .encodeDeclineMessage(m.getGroupId(), m.getTimestamp() + 1, + m.getMessageId(), m.getSessionId()); + c1.getClientHelper().addLocalMessage(msg, new BdfDictionary(), true); + + // sync fake DECLINE back to introducer + sync1To0(1, true); + + assertTrue(listener0.aborted); + } + /** * One introducee sends two AUTH messages. * The introducer should notice this and ABORT the session. @@ -1093,6 +1129,9 @@ public class IntroductionIntegrationTest if (type == ACCEPT) { //noinspection ConstantConditions return c0.getMessageParser().parseAcceptMessage(m, body); + } else if (type == DECLINE) { + //noinspection ConstantConditions + return c0.getMessageParser().parseDeclineMessage(m, body); } else if (type == AUTH) { //noinspection ConstantConditions return c0.getMessageParser().parseAuthMessage(m, body); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java index b1ad9caa9..4f52aa4de 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java @@ -268,6 +268,21 @@ public class IntroductionValidatorTest extends ValidatorTestCase { validator.validateMessage(message, group, body); } + @Test(expected = FormatException.class) + public void testRejectsInvalidPreviousMsgIdForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), + 1, getRandomBytes(MAC_BYTES), + signature); + validator.validateMessage(message, group, body); + } + + @Test(expected = FormatException.class) + public void testRejectsPreviousMsgIdNullForAuth() throws Exception { + BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), null, + getRandomBytes(MAC_BYTES), signature); + validator.validateMessage(message, group, body); + } + @Test(expected = FormatException.class) public void testRejectsTooShortMacForAuth() throws Exception { BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(), @@ -358,6 +373,14 @@ public class IntroductionValidatorTest extends ValidatorTestCase { validator.validateMessage(message, group, body); } + @Test(expected = FormatException.class) + public void testRejectsPreviousMsgIdNullForActivate() throws Exception { + BdfList body = + BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), null, + mac); + validator.validateMessage(message, group, body); + } + @Test(expected = FormatException.class) public void testRejectsInvalidMacForActivate() throws Exception { BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), From 99dba69c87df88d48b85892a6d5d639ae43b5c54 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 27 Apr 2018 11:29:10 -0300 Subject: [PATCH 21/21] 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. One contact might have deleted the other, but not vice versa. So they have mismatching state that needs to be reset. See #2 for more information. --- .../IntroduceeProtocolEngine.java | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index 322e46a6b..c0cf65a07 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -419,42 +419,44 @@ class IntroduceeProtocolEngine s.getRemote().acceptTimestamp); if (timestamp == -1) throw new AssertionError(); - boolean contactAdded = false; + Map keys = null; try { contactManager .addContact(txn, s.getRemote().author, localAuthor.getId(), false, true); - contactAdded = 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()); + + // bind the keys to the new contact + //noinspection ConstantConditions + keys = keyManager + .addUnboundKeys(txn, new SecretKey(s.getMasterKey()), + timestamp, s.getRemote().alice); + keyManager.bindKeys(txn, c.getId(), keys); + + // add signed transport properties for the contact + //noinspection ConstantConditions + transportPropertyManager.addRemoteProperties(txn, c.getId(), + s.getRemote().transportProperties); + + // Broadcast IntroductionSucceededEvent, because contact got added + IntroductionSucceededEvent e = new IntroductionSucceededEvent(c); + txn.attach(e); } catch (ContactExistsException e) { // Ignore this, because the other introducee might have deleted us. // So we still want updated transport properties // and new transport keys. } - Contact c = contactManager.getContact(txn, s.getRemote().author.getId(), - localAuthor.getId()); - - // bind the keys to the new (or existing) contact - //noinspection ConstantConditions - Map keys = keyManager - .addUnboundKeys(txn, new SecretKey(s.getMasterKey()), - timestamp, s.getRemote().alice); - keyManager.bindKeys(txn, c.getId(), keys); - - // add signed transport properties for the contact - //noinspection ConstantConditions - transportPropertyManager.addRemoteProperties(txn, c.getId(), - s.getRemote().transportProperties); // send ACTIVATE message with a MAC byte[] mac = crypto.activateMac(s); Message sent = sendActivateMessage(txn, s, getLocalTimestamp(s), mac); - if (contactAdded) { - // Broadcast IntroductionSucceededEvent, because contact got added - IntroductionSucceededEvent e = new IntroductionSucceededEvent(c); - txn.attach(e); - } - // Move to AWAIT_ACTIVATE state and clear key material from session return IntroduceeSession.awaitActivate(s, m, sent, keys); } @@ -469,12 +471,15 @@ class IntroduceeProtocolEngine try { crypto.verifyActivateMac(m.getMac(), s); } catch (GeneralSecurityException e) { - // TODO remove transport keys? return abort(txn, s); } - // Activate transport keys - keyManager.activateKeys(txn, s.getTransportKeys()); + // We might not have added transport keys + // if the contact existed when the remote AUTH was received. + if (s.getTransportKeys() != null) { + // Activate transport keys + keyManager.activateKeys(txn, s.getTransportKeys()); + } // Move back to START state return IntroduceeSession.clear(s, START, s.getLastLocalMessageId(),