diff --git a/briar-api/src/org/briarproject/api/privategroup/MessageType.java b/briar-api/src/org/briarproject/api/privategroup/MessageType.java new file mode 100644 index 0000000000000000000000000000000000000000..afe6a07d1c3dd17b37fe7e4498b45b3fb3e06402 --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/MessageType.java @@ -0,0 +1,30 @@ +package org.briarproject.api.privategroup; + +public enum MessageType { + NEW_MEMBER(0), + JOIN(1), + POST(2); + + int value; + + MessageType(int value) { + this.value = value; + } + + public static MessageType valueOf(int value) { + switch (value) { + case 0: + return NEW_MEMBER; + case 1: + return JOIN; + case 2: + return POST; + default: + throw new IllegalArgumentException(); + } + } + + public int getInt() { + return value; + } +} \ No newline at end of file diff --git a/briar-core/src/org/briarproject/privategroup/Constants.java b/briar-core/src/org/briarproject/privategroup/Constants.java index 7a1a4495bf104b7c083cf96bbb5965f20024c7c1..b392abb8f181e3d35d938b6900b5fc3db3e4c8f8 100644 --- a/briar-core/src/org/briarproject/privategroup/Constants.java +++ b/briar-core/src/org/briarproject/privategroup/Constants.java @@ -1,8 +1,14 @@ package org.briarproject.privategroup; +import static org.briarproject.clients.BdfConstants.MSG_KEY_READ; + interface Constants { // Database keys - String KEY_READ = "read"; + String KEY_TYPE = "type"; + String KEY_TIMESTAMP = "timestamp"; + String KEY_READ = MSG_KEY_READ; + String KEY_AUTHOR_NAME = "authorName"; + String KEY_AUTHOR_PUBLIC_KEY = "authorPublicKey"; } diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java index 75a8913c07a677efdeb4d5ef01cb85487bb473bf..1bd94100b04a7c1a0c198d25b2d7a7001b6f7ffc 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java +++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java @@ -3,11 +3,12 @@ package org.briarproject.privategroup; import org.briarproject.api.FormatException; import org.briarproject.api.clients.BdfMessageContext; import org.briarproject.api.clients.ClientHelper; -import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfList; import org.briarproject.api.data.MetadataEncoder; -import org.briarproject.api.identity.AuthorFactory; +import org.briarproject.api.privategroup.MessageType; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.InvalidMessageException; import org.briarproject.api.sync.Message; @@ -15,29 +16,178 @@ import org.briarproject.api.sync.MessageId; import org.briarproject.api.system.Clock; import org.briarproject.clients.BdfMessageValidator; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; +import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH; +import static org.briarproject.privategroup.Constants.KEY_AUTHOR_NAME; +import static org.briarproject.privategroup.Constants.KEY_AUTHOR_PUBLIC_KEY; +import static org.briarproject.privategroup.Constants.KEY_READ; +import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP; +import static org.briarproject.privategroup.Constants.KEY_TYPE; + class GroupMessageValidator extends BdfMessageValidator { - private final CryptoComponent crypto; - private final AuthorFactory authorFactory; + private final PrivateGroupFactory groupFactory; - GroupMessageValidator(CryptoComponent crypto, AuthorFactory authorFactory, + GroupMessageValidator(PrivateGroupFactory groupFactory, ClientHelper clientHelper, MetadataEncoder metadataEncoder, Clock clock) { super(clientHelper, metadataEncoder, clock); - this.crypto = crypto; - this.authorFactory = authorFactory; + this.groupFactory = groupFactory; } @Override protected BdfMessageContext validateMessage(Message m, Group g, BdfList body) throws InvalidMessageException, FormatException { + checkSize(body, 4, 7); + + // message type (int) + int type = body.getLong(0).intValue(); + body.removeElementAt(0); + + // member_name (string) + String member_name = body.getString(0); + checkLength(member_name, 1, MAX_AUTHOR_NAME_LENGTH); + + // member_public_key (raw) + byte[] member_public_key = body.getRaw(1); + checkLength(member_public_key, 1, MAX_PUBLIC_KEY_LENGTH); + + BdfMessageContext c; + switch (MessageType.valueOf(type)) { + case NEW_MEMBER: + c = validateNewMember(m, g, body, member_name, + member_public_key); + addMessageMetadata(c, member_name, member_public_key, + m.getTimestamp()); + break; + case JOIN: + c = validateJoin(m, g, body, member_name, member_public_key); + addMessageMetadata(c, member_name, member_public_key, + m.getTimestamp()); + break; + case POST: + c = validatePost(m, g, body, member_name, member_public_key); + break; + default: + throw new InvalidMessageException("Unknown Message Type"); + } + c.getDictionary().put(KEY_TYPE, type); + return c; + } + + private BdfMessageContext validateNewMember(Message m, Group g, + BdfList body, String member_name, byte[] member_public_key) + throws InvalidMessageException, FormatException { + + // The content is a BDF list with three elements + checkSize(body, 3); + + // signature (raw) + // signature with the creator's private key over a list with 4 elements + byte[] signature = body.getRaw(2); + checkLength(signature, 1, MAX_SIGNATURE_LENGTH); + + // Verify Signature + BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), member_name, + member_public_key); + PrivateGroup group = groupFactory.parsePrivateGroup(g); + byte[] creatorPublicKey = group.getAuthor().getPublicKey(); + clientHelper.verifySignature(signature, creatorPublicKey, signed); + + // Return the metadata and no dependencies + BdfDictionary meta = new BdfDictionary(); + return new BdfMessageContext(meta); + } + + private BdfMessageContext validateJoin(Message m, Group g, BdfList body, + String member_name, byte[] member_public_key) + throws InvalidMessageException, FormatException { + + // The content is a BDF list with four elements + checkSize(body, 4); + + // new_member_id (raw) + // the identifier of a new member message + // with the same member_name and member_public_key + byte[] new_member_id = body.getRaw(2); + checkLength(new_member_id, MessageId.LENGTH); + + // signature (raw) + // a signature with the member's private key over a list with 5 elements + byte[] signature = body.getRaw(3); + checkLength(signature, 1, MAX_SIGNATURE_LENGTH); + + // Verify Signature + BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), member_name, + member_public_key, new_member_id); + clientHelper.verifySignature(signature, member_public_key, signed); + + // The new member message is a dependency + Collection<MessageId> dependencies = + Collections.singleton(new MessageId(new_member_id)); + + // Return the metadata and dependencies BdfDictionary meta = new BdfDictionary(); - Collection<MessageId> dependencies = Collections.emptyList(); return new BdfMessageContext(meta, dependencies); } + private BdfMessageContext validatePost(Message m, Group g, BdfList body, + String member_name, byte[] member_public_key) + throws InvalidMessageException, FormatException { + + // The content is a BDF list with six elements + checkSize(body, 6); + + // parent_id (raw or null) + // the identifier of the post to which this is a reply, if any + byte[] parent_id = body.getOptionalRaw(2); + if (parent_id != null) { + checkLength(parent_id, MessageId.LENGTH); + } + + // previous_message_id (raw) + // the identifier of the member's previous post or join message + byte[] previous_message_id = body.getRaw(3); + checkLength(previous_message_id, MessageId.LENGTH); + + // content (string) + String content = body.getString(4); + checkLength(content, 0, MAX_GROUP_POST_BODY_LENGTH); + + // signature (raw) + // a signature with the member's private key over a list with 7 elements + byte[] signature = body.getRaw(5); + checkLength(signature, 1, MAX_SIGNATURE_LENGTH); + + // Verify Signature + BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), member_name, + member_public_key, parent_id, previous_message_id, content); + clientHelper.verifySignature(signature, member_public_key, signed); + + // The parent post, if any, + // and the member's previous message are dependencies + Collection<MessageId> dependencies = new ArrayList<MessageId>(); + if (parent_id != null) dependencies.add(new MessageId(parent_id)); + dependencies.add(new MessageId(previous_message_id)); + + // Return the metadata and dependencies + BdfDictionary meta = new BdfDictionary(); + return new BdfMessageContext(meta, dependencies); + } + + private void addMessageMetadata(BdfMessageContext c, String authorName, + byte[] pubKey, long time) { + c.getDictionary().put(KEY_TIMESTAMP, time); + c.getDictionary().put(KEY_READ, false); + c.getDictionary().put(KEY_AUTHOR_NAME, authorName); + c.getDictionary().put(KEY_AUTHOR_PUBLIC_KEY, pubKey); + } + } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index 05c2d4021c4c766a2cdbcb89baea7e792a594aeb..ee861267e916d5f870017ae105e4b2ac1de228f6 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -198,6 +198,12 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements trackIncomingMessage(txn, m); + + // TODO POST timestamp must be greater than the timestamps of the parent post, if any, and the member's previous message + + // TODO JOIN timestamp must be equal to the timestamp of the new member message. + // TODO JOIN new_member_id must be the identifier of a NEW_MEMBER message with the same member_name and member_public_key + return true; } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java index 570f697a629d952be8790dd5b27e04e1a21a2a90..55fa0894a653e4541dec26253ef99db850684608 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java @@ -2,9 +2,7 @@ package org.briarproject.privategroup; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.contact.ContactManager; -import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.data.MetadataEncoder; -import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.messaging.ConversationManager; import org.briarproject.api.privategroup.GroupMessageFactory; @@ -59,11 +57,11 @@ public class PrivateGroupModule { @Provides @Singleton GroupMessageValidator provideGroupMessageValidator( - ValidationManager validationManager, CryptoComponent crypto, - AuthorFactory authorFactory, ClientHelper clientHelper, + PrivateGroupFactory groupFactory, + ValidationManager validationManager, ClientHelper clientHelper, MetadataEncoder metadataEncoder, Clock clock) { - GroupMessageValidator validator = new GroupMessageValidator(crypto, - authorFactory, clientHelper, metadataEncoder, clock); + GroupMessageValidator validator = new GroupMessageValidator( + groupFactory, clientHelper, metadataEncoder, clock); validationManager.registerMessageValidator( PrivateGroupManagerImpl.CLIENT_ID, validator); return validator;