From 155c6a5613e4c23ad012b2d77630f1f9b5e4851c Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Mon, 16 Apr 2018 16:00:17 -0300 Subject: [PATCH] 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 3b12c5cf75..b9f772d2b0 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 799722344b..66a4a3b563 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 0000000000..6b91b33c02 --- /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 0000000000..9d71124076 --- /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 0000000000..b9bf92b6c4 --- /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<TransportId, TransportProperties> transportProperties; + + protected AcceptMessage(MessageId messageId, GroupId groupId, + long timestamp, @Nullable MessageId previousMessageId, + SessionId sessionId, + byte[] ephemeralPublicKey, + Map<TransportId, TransportProperties> 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<TransportId, TransportProperties> 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 0000000000..0d5e935b48 --- /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 0000000000..5833a64d68 --- /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 0000000000..c419704c5c --- /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 0000000000..24a252f5ef --- /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 0000000000..1a64538ea1 --- /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 0000000000..ce8359701f --- /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 0000000000..25371b68cb --- /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 0000000000..bb34b7da3e --- /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 0000000000..470fb39662 --- /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 0000000000..fc01094979 --- /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 196ce09ca6..3148da439f 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 157c4ce79d..f001fb6800 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<MessageId> dependencies = messageContext.getDependencies(); if (previousMsgId == null) { assertTrue(dependencies.isEmpty()); -- GitLab