From 712524867769e27e1d59b3712ed3bd7f87c764c4 Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Thu, 3 Nov 2016 12:24:56 -0200 Subject: [PATCH] Remove new member announcement and add signature to invitation --- .../briarproject/PrivateGroupManagerTest.java | 159 +++++++++++----- .../creation/CreateGroupControllerImpl.java | 13 +- .../api/clients/ContactGroupFactory.java | 9 +- .../api/privategroup/GroupMessageFactory.java | 32 ++-- .../api/privategroup/MessageType.java | 5 +- .../api/privategroup/PrivateGroupManager.java | 6 +- .../clients/ContactGroupFactoryImpl.java | 7 + .../privategroup/GroupMessageFactoryImpl.java | 41 ++--- .../privategroup/GroupMessageValidator.java | 172 +++++++++--------- .../privategroup/PrivateGroupManagerImpl.java | 47 +---- .../privategroup/PrivateGroupModule.java | 9 +- 11 files changed, 253 insertions(+), 247 deletions(-) diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java index 75d8791084..786966df5a 100644 --- a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java @@ -2,12 +2,15 @@ package org.briarproject; import net.jodah.concurrentunit.Waiter; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.ContactGroupFactory; import org.briarproject.api.clients.MessageTracker.GroupCount; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.KeyPair; import org.briarproject.api.crypto.SecretKey; +import org.briarproject.api.data.BdfList; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; import org.briarproject.api.event.Event; @@ -24,6 +27,8 @@ import org.briarproject.api.privategroup.JoinMessageHeader; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; +import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.SyncSession; @@ -53,6 +58,7 @@ import java.util.logging.Logger; import javax.inject.Inject; import static org.briarproject.TestPluginsModule.MAX_LATENCY; +import static org.briarproject.TestUtils.getRandomBytes; import static org.briarproject.api.identity.Author.Status.VERIFIED; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.INVALID; @@ -72,18 +78,23 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { private LocalAuthor author0, author1; private PrivateGroup privateGroup0; private GroupId groupId0; - private GroupMessage newMemberMsg0; @Inject Clock clock; @Inject AuthorFactory authorFactory; @Inject + ClientHelper clientHelper; + @Inject CryptoComponent crypto; @Inject + ContactGroupFactory contactGroupFactory; + @Inject PrivateGroupFactory privateGroupFactory; @Inject GroupMessageFactory groupMessageFactory; + @Inject + GroupInvitationManager groupInvitationManager; // objects accessed from background threads need to be volatile private volatile Waiter validationWaiter; @@ -222,20 +233,6 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { // assert that message did not arrive assertEquals(2, groupManager1.getHeaders(groupId0).size()); - - // create and add test message with previousMsgId of newMemberMsg - previousMsgId = newMemberMsg0.getMessage().getId(); - msg = groupMessageFactory - .createGroupMessage(groupId0, clock.currentTimeMillis(), null, - author0, "test", previousMsgId); - groupManager0.addLocalMessage(msg); - - // sync test message - sync0To1(); - validationWaiter.await(TIMEOUT, 1); - - // assert that message did not arrive - assertEquals(2, groupManager1.getHeaders(groupId0).size()); } @Test @@ -323,15 +320,13 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { addDefaultContacts(); listenToEvents(); - // author0 joins privateGroup0 with later timestamp + // author0 joins privateGroup0 with wrong join message long joinTime = clock.currentTimeMillis(); - GroupMessage newMemberMsg = groupMessageFactory - .createNewMemberMessage(groupId0, joinTime, author0, author0); - GroupMessage joinMsg = groupMessageFactory - .createJoinMessage(groupId0, joinTime + 1, author0, - newMemberMsg.getMessage().getId()); - groupManager0.addPrivateGroup(privateGroup0, newMemberMsg, joinMsg); - assertEquals(joinMsg.getMessage().getId(), + GroupMessage joinMsg0 = groupMessageFactory + .createJoinMessage(privateGroup0.getId(), joinTime, author0, + joinTime, getRandomBytes(12)); + groupManager0.addPrivateGroup(privateGroup0, joinMsg0); + assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); // make group visible to 1 @@ -342,15 +337,21 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { t0.getDatabaseComponent().commitTransaction(txn0); t0.getDatabaseComponent().endTransaction(txn0); - // author1 joins privateGroup0 and refers to wrong NEW_MEMBER message - joinMsg = groupMessageFactory - .createJoinMessage(groupId0, joinTime, author1, - newMemberMsg.getMessage().getId()); + // author1 joins privateGroup0 with wrong timestamp joinTime = clock.currentTimeMillis(); - newMemberMsg = groupMessageFactory - .createNewMemberMessage(groupId0, joinTime, author0, author1); - groupManager1.addPrivateGroup(privateGroup0, newMemberMsg, joinMsg); - assertEquals(joinMsg.getMessage().getId(), + long inviteTime = joinTime; + Group invitationGroup = contactGroupFactory + .createContactGroup(groupInvitationManager.getClientId(), + author0.getId(), author1.getId()); + BdfList toSign = BdfList.of(0, inviteTime, invitationGroup.getId(), + privateGroup0.getId()); + byte[] creatorSignature = + clientHelper.sign(toSign, author0.getPrivateKey()); + GroupMessage joinMsg1 = groupMessageFactory + .createJoinMessage(privateGroup0.getId(), joinTime, author1, + inviteTime, creatorSignature); + groupManager1.addPrivateGroup(privateGroup0, joinMsg1); + assertEquals(joinMsg1.getMessage().getId(), groupManager1.getPreviousMsgId(groupId0)); // make group visible to 0 @@ -363,14 +364,73 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { // sync join messages sync0To1(); - deliveryWaiter.await(TIMEOUT, 1); validationWaiter.await(TIMEOUT, 1); // assert that 0 never joined the group from 1's perspective assertEquals(1, groupManager1.getHeaders(groupId0).size()); sync1To0(); + validationWaiter.await(TIMEOUT, 1); + + // assert that 1 never joined the group from 0's perspective + assertEquals(1, groupManager0.getHeaders(groupId0).size()); + } + + @Test + public void testWrongJoinMessageSignature() throws Exception { + addDefaultIdentities(); + addDefaultContacts(); + listenToEvents(); + + // author0 joins privateGroup0 properly + long joinTime = clock.currentTimeMillis(); + GroupMessage joinMsg0 = groupMessageFactory + .createJoinMessage(privateGroup0.getId(), joinTime, author0); + groupManager0.addPrivateGroup(privateGroup0, joinMsg0); + assertEquals(joinMsg0.getMessage().getId(), + groupManager0.getPreviousMsgId(groupId0)); + + // make group visible to 1 + Transaction txn0 = t0.getDatabaseComponent().startTransaction(false); + t0.getDatabaseComponent() + .setVisibleToContact(txn0, contactId1, privateGroup0.getId(), + true); + txn0.setComplete(); + t0.getDatabaseComponent().endTransaction(txn0); + + // author1 joins privateGroup0 with wrong timestamp + joinTime = clock.currentTimeMillis(); + long inviteTime = joinTime - 1; + Group invitationGroup = contactGroupFactory + .createContactGroup(groupInvitationManager.getClientId(), + author0.getId(), author1.getId()); + BdfList toSign = BdfList.of(0, inviteTime, invitationGroup.getId(), + privateGroup0.getId()); + byte[] creatorSignature = + clientHelper.sign(toSign, author1.getPrivateKey()); + GroupMessage joinMsg1 = groupMessageFactory + .createJoinMessage(privateGroup0.getId(), joinTime, author1, + inviteTime, creatorSignature); + groupManager1.addPrivateGroup(privateGroup0, joinMsg1); + assertEquals(joinMsg1.getMessage().getId(), + groupManager1.getPreviousMsgId(groupId0)); + + // make group visible to 0 + Transaction txn1 = t1.getDatabaseComponent().startTransaction(false); + t1.getDatabaseComponent() + .setVisibleToContact(txn1, contactId0, privateGroup0.getId(), + true); + txn1.setComplete(); + t1.getDatabaseComponent().endTransaction(txn1); + + // sync join messages + sync0To1(); deliveryWaiter.await(TIMEOUT, 1); + + // assert that 0 never joined the group from 1's perspective + assertEquals(2, groupManager1.getHeaders(groupId0).size()); + + sync1To0(); validationWaiter.await(TIMEOUT, 1); // assert that 1 never joined the group from 0's perspective @@ -452,14 +512,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { private void addGroup() throws Exception { // author0 joins privateGroup0 long joinTime = clock.currentTimeMillis(); - newMemberMsg0 = groupMessageFactory - .createNewMemberMessage(privateGroup0.getId(), joinTime, - author0, author0); - GroupMessage joinMsg = groupMessageFactory - .createJoinMessage(privateGroup0.getId(), joinTime, author0, - newMemberMsg0.getMessage().getId()); - groupManager0.addPrivateGroup(privateGroup0, newMemberMsg0, joinMsg); - assertEquals(joinMsg.getMessage().getId(), + GroupMessage joinMsg0 = groupMessageFactory + .createJoinMessage(privateGroup0.getId(), joinTime, author0); + groupManager0.addPrivateGroup(privateGroup0, joinMsg0); + assertEquals(joinMsg0.getMessage().getId(), groupManager0.getPreviousMsgId(groupId0)); // make group visible to 1 @@ -472,14 +528,19 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { // author1 joins privateGroup0 joinTime = clock.currentTimeMillis(); - GroupMessage newMemberMsg1 = groupMessageFactory - .createNewMemberMessage(privateGroup0.getId(), joinTime, - author0, author1); - joinMsg = groupMessageFactory + long inviteTime = joinTime - 1; + Group invitationGroup = contactGroupFactory + .createContactGroup(groupInvitationManager.getClientId(), + author0.getId(), author1.getId()); + BdfList toSign = BdfList.of(0, inviteTime, invitationGroup.getId(), + privateGroup0.getId()); + byte[] creatorSignature = + clientHelper.sign(toSign, author0.getPrivateKey()); + GroupMessage joinMsg1 = groupMessageFactory .createJoinMessage(privateGroup0.getId(), joinTime, author1, - newMemberMsg1.getMessage().getId()); - groupManager1.addPrivateGroup(privateGroup0, newMemberMsg1, joinMsg); - assertEquals(joinMsg.getMessage().getId(), + inviteTime, creatorSignature); + groupManager1.addPrivateGroup(privateGroup0, joinMsg1); + assertEquals(joinMsg1.getMessage().getId(), groupManager1.getPreviousMsgId(groupId0)); // make group visible to 0 @@ -492,9 +553,9 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest { // sync join messages sync0To1(); - deliveryWaiter.await(TIMEOUT, 2); + deliveryWaiter.await(TIMEOUT, 1); sync1To0(); - deliveryWaiter.await(TIMEOUT, 2); + deliveryWaiter.await(TIMEOUT, 1); } private void sync0To1() throws IOException, TimeoutException { diff --git a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java index db661c43da..b5632e9bf3 100644 --- a/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/creation/CreateGroupControllerImpl.java @@ -82,29 +82,24 @@ public class CreateGroupControllerImpl extends DbControllerImpl LOG.info("Creating group..."); PrivateGroup group = groupFactory.createPrivateGroup(name, author); - LOG.info("Creating new member announcement..."); - GroupMessage newMemberMsg = groupMessageFactory - .createNewMemberMessage(group.getId(), - clock.currentTimeMillis(), author, author); LOG.info("Creating new join announcement..."); GroupMessage joinMsg = groupMessageFactory .createJoinMessage(group.getId(), - newMemberMsg.getMessage().getTimestamp(), - author, newMemberMsg.getMessage().getId()); - storeGroup(group, newMemberMsg, joinMsg, handler); + clock.currentTimeMillis(), author); + storeGroup(group, joinMsg, handler); } }); } private void storeGroup(final PrivateGroup group, - final GroupMessage newMemberMsg, final GroupMessage joinMsg, + final GroupMessage joinMsg, final ResultExceptionHandler<GroupId, DbException> handler) { runOnDbThread(new Runnable() { @Override public void run() { LOG.info("Adding group to database..."); try { - groupManager.addPrivateGroup(group, newMemberMsg, joinMsg); + groupManager.addPrivateGroup(group, joinMsg); handler.onResult(group.getId()); } catch (DbException e) { if (LOG.isLoggable(WARNING)) diff --git a/briar-api/src/org/briarproject/api/clients/ContactGroupFactory.java b/briar-api/src/org/briarproject/api/clients/ContactGroupFactory.java index 7043bb30d1..705934de6d 100644 --- a/briar-api/src/org/briarproject/api/clients/ContactGroupFactory.java +++ b/briar-api/src/org/briarproject/api/clients/ContactGroupFactory.java @@ -1,9 +1,9 @@ package org.briarproject.api.clients; import org.briarproject.api.contact.Contact; +import org.briarproject.api.identity.AuthorId; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; -import org.briarproject.api.sync.GroupFactory; public interface ContactGroupFactory { @@ -13,4 +13,11 @@ public interface ContactGroupFactory { /** Creates a group for the given client to share with the given contact. */ Group createContactGroup(ClientId clientId, Contact contact); + /** + * Creates a group for the given client to share between the given authors + * identified by their AuthorIds. + */ + Group createContactGroup(ClientId clientId, AuthorId authorId1, + AuthorId authorId2); + } diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java index 26e7ae9c7b..3b50246958 100644 --- a/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java +++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessageFactory.java @@ -10,33 +10,29 @@ import org.jetbrains.annotations.Nullable; public interface GroupMessageFactory { /** - * Creates a new member announcement that contains the joiner's identity - * and is signed by the creator. - * <p> - * When a new member accepts an invitation to the group, - * the creator sends this new member announcement to the group. + * Creates a join announcement message for the creator of a group. * - * @param groupId The ID of the group the new member joined - * @param timestamp The current timestamp - * @param creator The creator of the group with {@param groupId} - * @param member The new member that has just accepted an invitation + * @param groupId The ID of the Group that is being joined + * @param timestamp Must be greater than the timestamp of the invitation message + * @param creator The creator's LocalAuthor */ @CryptoExecutor - GroupMessage createNewMemberMessage(GroupId groupId, long timestamp, - LocalAuthor creator, Author member); + GroupMessage createJoinMessage(GroupId groupId, long timestamp, + LocalAuthor creator); /** - * Creates a join announcement message - * that depends on a previous new member announcement. + * Creates a join announcement message for a joining member. * - * @param groupId The ID of the Group that is being joined - * @param timestamp Must be equal to the timestamp of the new member message - * @param member Our own LocalAuthor - * @param newMemberId The MessageId of the new member message + * @param groupId The ID of the Group that is being joined + * @param timestamp Must be greater than the timestamp of the + * invitation message + * @param member The member's LocalAuthor + * @param inviteTimestamp The timestamp of the group invitation message + * @param creatorSignature The creator's signature from the group invitation */ @CryptoExecutor GroupMessage createJoinMessage(GroupId groupId, long timestamp, - LocalAuthor member, MessageId newMemberId); + LocalAuthor member, long inviteTimestamp, byte[] creatorSignature); /** * Creates a group message diff --git a/briar-api/src/org/briarproject/api/privategroup/MessageType.java b/briar-api/src/org/briarproject/api/privategroup/MessageType.java index 7cffb0df31..8b5cf7d578 100644 --- a/briar-api/src/org/briarproject/api/privategroup/MessageType.java +++ b/briar-api/src/org/briarproject/api/privategroup/MessageType.java @@ -1,9 +1,8 @@ package org.briarproject.api.privategroup; public enum MessageType { - NEW_MEMBER(0), - JOIN(1), - POST(2); + JOIN(0), + POST(1); int value; diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java index ded7a17028..d0ed0d7500 100644 --- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java @@ -21,12 +21,10 @@ public interface PrivateGroupManager extends MessageTracker { * Adds a new private group and joins it. * * @param group The private group to add - * @param newMemberMsg The creator's message announcing herself as - * first new member * @param joinMsg The creator's own join message */ - void addPrivateGroup(PrivateGroup group, GroupMessage newMemberMsg, - GroupMessage joinMsg) throws DbException; + void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg) + throws DbException; /** Removes a dissolved private group. */ void removePrivateGroup(GroupId g) throws DbException; diff --git a/briar-core/src/org/briarproject/clients/ContactGroupFactoryImpl.java b/briar-core/src/org/briarproject/clients/ContactGroupFactoryImpl.java index d3a28dd994..f5cbaf8e9d 100644 --- a/briar-core/src/org/briarproject/clients/ContactGroupFactoryImpl.java +++ b/briar-core/src/org/briarproject/clients/ContactGroupFactoryImpl.java @@ -41,6 +41,13 @@ class ContactGroupFactoryImpl implements ContactGroupFactory { return groupFactory.createGroup(clientId, descriptor); } + @Override + public Group createContactGroup(ClientId clientId, AuthorId authorId1, + AuthorId authorId2) { + byte[] descriptor = createGroupDescriptor(authorId1, authorId2); + return groupFactory.createGroup(clientId, descriptor); + } + private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) { try { if (Bytes.COMPARATOR.compare(local, remote) < 0) diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java b/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java index 228b642544..aeb478001b 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java +++ b/briar-core/src/org/briarproject/privategroup/GroupMessageFactoryImpl.java @@ -3,7 +3,6 @@ package org.briarproject.privategroup; import org.briarproject.api.FormatException; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.data.BdfList; -import org.briarproject.api.identity.Author; import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.privategroup.GroupMessage; @@ -18,7 +17,6 @@ import java.security.GeneralSecurityException; import javax.inject.Inject; import static org.briarproject.api.privategroup.MessageType.JOIN; -import static org.briarproject.api.privategroup.MessageType.NEW_MEMBER; import static org.briarproject.api.privategroup.MessageType.POST; @NotNullByDefault @@ -32,45 +30,34 @@ class GroupMessageFactoryImpl implements GroupMessageFactory { } @Override - public GroupMessage createNewMemberMessage(GroupId groupId, long timestamp, - LocalAuthor creator, Author member) { - try { - // Generate the signature - int type = NEW_MEMBER.getInt(); - BdfList toSign = BdfList.of(groupId, timestamp, type, - member.getName(), member.getPublicKey()); - byte[] signature = - clientHelper.sign(toSign, creator.getPrivateKey()); - - // Compose the message - BdfList body = - BdfList.of(type, member.getName(), - member.getPublicKey(), signature); - Message m = clientHelper.createMessage(groupId, timestamp, body); + public GroupMessage createJoinMessage(GroupId groupId, long timestamp, + LocalAuthor creator) { - return new GroupMessage(m, null, member); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } catch (FormatException e) { - throw new RuntimeException(e); - } + return createJoinMessage(groupId, timestamp, creator, null); } @Override public GroupMessage createJoinMessage(GroupId groupId, long timestamp, - LocalAuthor member, MessageId newMemberId) { + LocalAuthor member, long inviteTimestamp, byte[] creatorSignature) { + + BdfList invite = BdfList.of(inviteTimestamp, creatorSignature); + return createJoinMessage(groupId, timestamp, member, invite); + } + + private GroupMessage createJoinMessage(GroupId groupId, long timestamp, + LocalAuthor member, @Nullable BdfList invite) { try { // Generate the signature int type = JOIN.getInt(); BdfList toSign = BdfList.of(groupId, timestamp, type, - member.getName(), member.getPublicKey(), newMemberId); - byte[] signature = + member.getName(), member.getPublicKey(), invite); + byte[] memberSignature = clientHelper.sign(toSign, member.getPrivateKey()); // Compose the message BdfList body = BdfList.of(type, member.getName(), - member.getPublicKey(), newMemberId, signature); + member.getPublicKey(), invite, memberSignature); Message m = clientHelper.createMessage(groupId, timestamp, body); return new GroupMessage(m, null, member); diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java index 25bf14730c..cddbcd9299 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java +++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java @@ -3,6 +3,7 @@ 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.clients.ContactGroupFactory; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfList; import org.briarproject.api.data.MetadataEncoder; @@ -11,6 +12,7 @@ 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.privategroup.invitation.GroupInvitationManager; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.InvalidMessageException; import org.briarproject.api.sync.Message; @@ -21,19 +23,16 @@ import org.briarproject.clients.BdfMessageValidator; import java.security.GeneralSecurityException; 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.MessageType.JOIN; -import static org.briarproject.api.privategroup.MessageType.NEW_MEMBER; import static org.briarproject.api.privategroup.MessageType.POST; import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH; import static org.briarproject.privategroup.Constants.KEY_MEMBER_ID; import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME; import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY; -import static org.briarproject.privategroup.Constants.KEY_NEW_MEMBER_MSG_ID; import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID; import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID; import static org.briarproject.privategroup.Constants.KEY_READ; @@ -42,52 +41,50 @@ import static org.briarproject.privategroup.Constants.KEY_TYPE; class GroupMessageValidator extends BdfMessageValidator { + private final ContactGroupFactory contactGroupFactory; private final PrivateGroupFactory groupFactory; private final AuthorFactory authorFactory; + private final GroupInvitationManager groupInvitationManager; // TODO remove - GroupMessageValidator(PrivateGroupFactory groupFactory, + GroupMessageValidator(ContactGroupFactory contactGroupFactory, + PrivateGroupFactory groupFactory, ClientHelper clientHelper, MetadataEncoder metadataEncoder, - Clock clock, AuthorFactory authorFactory) { + Clock clock, AuthorFactory authorFactory, + GroupInvitationManager groupInvitationManager) { super(clientHelper, metadataEncoder, clock); + this.contactGroupFactory = contactGroupFactory; this.groupFactory = groupFactory; this.authorFactory = authorFactory; + this.groupInvitationManager = groupInvitationManager; } @Override protected BdfMessageContext validateMessage(Message m, Group g, BdfList body) throws InvalidMessageException, FormatException { - checkSize(body, 4, 7); + checkSize(body, 5, 7); // message type (int) int type = body.getLong(0).intValue(); - body.removeElementAt(0); // member_name (string) - String memberName = body.getString(0); + String memberName = body.getString(1); checkLength(memberName, 1, MAX_AUTHOR_NAME_LENGTH); // member_public_key (raw) - byte[] memberPublicKey = body.getRaw(1); + byte[] memberPublicKey = body.getRaw(2); checkLength(memberPublicKey, 1, MAX_PUBLIC_KEY_LENGTH); + Author member = authorFactory.createAuthor(memberName, memberPublicKey); BdfMessageContext c; switch (MessageType.valueOf(type)) { - case NEW_MEMBER: - c = validateNewMember(m, g, body, memberName, - memberPublicKey); - addMessageMetadata(c, memberName, memberPublicKey, - m.getTimestamp()); - break; case JOIN: - c = validateJoin(m, g, body, memberName, memberPublicKey); - addMessageMetadata(c, memberName, memberPublicKey, - m.getTimestamp()); + c = validateJoin(m, g, body, member); + addMessageMetadata(c, member, m.getTimestamp()); break; case POST: - c = validatePost(m, g, body, memberName, memberPublicKey); - addMessageMetadata(c, memberName, memberPublicKey, - m.getTimestamp()); + c = validatePost(m, g, body, member); + addMessageMetadata(c, member, m.getTimestamp()); break; default: throw new InvalidMessageException("Unknown Message Type"); @@ -96,104 +93,106 @@ class GroupMessageValidator extends BdfMessageValidator { return c; } - private BdfMessageContext validateNewMember(Message m, Group g, - BdfList body, String memberName, byte[] memberPublicKey) - 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(), NEW_MEMBER.getInt(), - memberName, memberPublicKey); - PrivateGroup group = groupFactory.parsePrivateGroup(g); - byte[] creatorPublicKey = group.getAuthor().getPublicKey(); - try { - clientHelper.verifySignature(signature, creatorPublicKey, signed); - } catch (GeneralSecurityException e) { - throw new InvalidMessageException(e); - } - - // Return the metadata and no dependencies - BdfDictionary meta = new BdfDictionary(); - return new BdfMessageContext(meta); - } - private BdfMessageContext validateJoin(Message m, Group g, BdfList body, - String memberName, byte[] memberPublicKey) + Author member) throws InvalidMessageException, FormatException { - // The content is a BDF list with four elements - checkSize(body, 4); + // The content is a BDF list with five elements + checkSize(body, 5); + PrivateGroup pg = groupFactory.parsePrivateGroup(g); + + // invite is null if the member is the creator of the private group + BdfList invite = body.getList(3, null); + if (invite == null) { + if (!member.equals(pg.getAuthor())) + throw new InvalidMessageException(); + } else { + // Otherwise invite is a list with two elements + checkSize(invite, 2); + + // invite_timestamp (int) + // join_timestamp must be greater than invite_timestamp + long inviteTimestamp = invite.getLong(0); + if (m.getTimestamp() <= inviteTimestamp) + throw new InvalidMessageException(); + + // creator_signature (raw) + byte[] creatorSignature = invite.getRaw(1); + checkLength(creatorSignature, 1, MAX_SIGNATURE_LENGTH); + + // derive invitation group + Group invitationGroup = contactGroupFactory + .createContactGroup(groupInvitationManager.getClientId(), + pg.getAuthor().getId(), member.getId()); + + // signature with the creator's private key + // over a list with four elements: + // invite_type (int), invite_timestamp (int), + // invitation_group_id (raw), and private_group_id (raw) + BdfList signed = + BdfList.of(0, inviteTimestamp, invitationGroup.getId(), + g.getId()); + try { + clientHelper.verifySignature(creatorSignature, + pg.getAuthor().getPublicKey(), signed); + } catch (GeneralSecurityException e) { + throw new InvalidMessageException(e); + } + } - // new_member_id (raw) - // the identifier of a new member message - // with the same member_name and member_public_key - byte[] newMemberId = body.getRaw(2); - checkLength(newMemberId, 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); + // member_signature (raw) + // a signature with the member's private key over a list with 6 elements + byte[] memberSignature = body.getRaw(4); + checkLength(memberSignature, 1, MAX_SIGNATURE_LENGTH); // Verify Signature BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), JOIN.getInt(), - memberName, memberPublicKey, newMemberId); + member.getName(), member.getPublicKey(), invite); try { - clientHelper.verifySignature(signature, memberPublicKey, signed); + clientHelper.verifySignature(memberSignature, member.getPublicKey(), + signed); } catch (GeneralSecurityException e) { throw new InvalidMessageException(e); } - // The new member message is a dependency - Collection<MessageId> dependencies = - Collections.singleton(new MessageId(newMemberId)); - - // Return the metadata and dependencies + // Return the metadata and no dependencies BdfDictionary meta = new BdfDictionary(); - meta.put(KEY_NEW_MEMBER_MSG_ID, newMemberId); - return new BdfMessageContext(meta, dependencies); + return new BdfMessageContext(meta); } private BdfMessageContext validatePost(Message m, Group g, BdfList body, - String memberName, byte[] memberPublicKey) + Author member) throws InvalidMessageException, FormatException { // The content is a BDF list with six elements - checkSize(body, 6); + checkSize(body, 7); // parent_id (raw or null) // the identifier of the post to which this is a reply, if any - byte[] parentId = body.getOptionalRaw(2); + byte[] parentId = body.getOptionalRaw(3); checkLength(parentId, MessageId.LENGTH); // previous_message_id (raw) // the identifier of the member's previous post or join message - byte[] previousMessageId = body.getRaw(3); + byte[] previousMessageId = body.getRaw(4); checkLength(previousMessageId, MessageId.LENGTH); // content (string) - String content = body.getString(4); + String content = body.getString(5); 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); + byte[] signature = body.getRaw(6); checkLength(signature, 1, MAX_SIGNATURE_LENGTH); // Verify Signature BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), POST.getInt(), - memberName, memberPublicKey, parentId, previousMessageId, - content); + member.getName(), member.getPublicKey(), parentId, + previousMessageId, content); try { - clientHelper.verifySignature(signature, memberPublicKey, signed); + clientHelper + .verifySignature(signature, member.getPublicKey(), signed); } catch (GeneralSecurityException e) { throw new InvalidMessageException(e); } @@ -211,14 +210,13 @@ class GroupMessageValidator extends BdfMessageValidator { return new BdfMessageContext(meta, dependencies); } - private void addMessageMetadata(BdfMessageContext c, String authorName, - byte[] pubKey, long time) { + private void addMessageMetadata(BdfMessageContext c, Author member, + long time) { c.getDictionary().put(KEY_TIMESTAMP, time); c.getDictionary().put(KEY_READ, false); - Author a = authorFactory.createAuthor(authorName, pubKey); - c.getDictionary().put(KEY_MEMBER_ID, a.getId()); - c.getDictionary().put(KEY_MEMBER_NAME, authorName); - c.getDictionary().put(KEY_MEMBER_PUBLIC_KEY, pubKey); + c.getDictionary().put(KEY_MEMBER_ID, member.getId()); + c.getDictionary().put(KEY_MEMBER_NAME, member.getName()); + c.getDictionary().put(KEY_MEMBER_PUBLIC_KEY, member.getPublicKey()); } } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index e96c7edf4c..db4a73dc37 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -49,14 +49,12 @@ import static org.briarproject.api.identity.Author.Status.OURSELVES; import static org.briarproject.api.identity.Author.Status.UNVERIFIED; import static org.briarproject.api.identity.Author.Status.VERIFIED; import static org.briarproject.api.privategroup.MessageType.JOIN; -import static org.briarproject.api.privategroup.MessageType.NEW_MEMBER; import static org.briarproject.api.privategroup.MessageType.POST; import static org.briarproject.privategroup.Constants.KEY_DISSOLVED; import static org.briarproject.privategroup.Constants.KEY_MEMBERS; import static org.briarproject.privategroup.Constants.KEY_MEMBER_ID; import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME; import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY; -import static org.briarproject.privategroup.Constants.KEY_NEW_MEMBER_MSG_ID; import static org.briarproject.privategroup.Constants.KEY_PARENT_MSG_ID; import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID; import static org.briarproject.privategroup.Constants.KEY_READ; @@ -95,8 +93,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } @Override - public void addPrivateGroup(PrivateGroup group, - GroupMessage newMemberMsg, GroupMessage joinMsg) + public void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg) throws DbException { Transaction txn = db.startTransaction(false); try { @@ -106,7 +103,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements new BdfEntry(KEY_DISSOLVED, false) ); clientHelper.mergeGroupMetadata(txn, group.getId(), meta); - announceNewMember(txn, newMemberMsg); joinPrivateGroup(txn, joinMsg); db.commitTransaction(txn); } catch (FormatException e) { @@ -116,14 +112,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } } - private void announceNewMember(Transaction txn, GroupMessage m) - throws DbException, FormatException { - BdfDictionary meta = new BdfDictionary(); - meta.put(KEY_TYPE, NEW_MEMBER.getInt()); - addMessageMetadata(meta, m, true); - clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); - } - private void joinPrivateGroup(Transaction txn, GroupMessage m) throws DbException, FormatException { BdfDictionary meta = new BdfDictionary(); @@ -315,8 +303,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements // get all authors we need to get the status for Set<AuthorId> authors = new HashSet<AuthorId>(); for (BdfDictionary meta : metadata.values()) { - if (meta.getLong(KEY_TYPE) == NEW_MEMBER.getInt()) - continue; byte[] idBytes = meta.getRaw(KEY_MEMBER_ID); authors.add(new AuthorId(idBytes)); } @@ -328,8 +314,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements // Parse the metadata for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) { BdfDictionary meta = entry.getValue(); - if (meta.getLong(KEY_TYPE) == NEW_MEMBER.getInt()) - continue; headers.add(getGroupMessageHeader(txn, g, entry.getKey(), meta, statuses)); } @@ -434,36 +418,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements MessageType type = MessageType.valueOf(meta.getLong(KEY_TYPE).intValue()); switch (type) { - case NEW_MEMBER: - // don't track incoming message, because it won't show in the UI - return true; case JOIN: - // new_member_id must be the identifier of a NEW_MEMBER message - byte[] newMemberIdBytes = - meta.getOptionalRaw(KEY_NEW_MEMBER_MSG_ID); - MessageId newMemberId = new MessageId(newMemberIdBytes); - BdfDictionary newMemberMeta = clientHelper - .getMessageMetadataAsDictionary(txn, newMemberId); - MessageType newMemberType = MessageType - .valueOf(newMemberMeta.getLong(KEY_TYPE).intValue()); - if (newMemberType != NEW_MEMBER) { - // FIXME throw new InvalidMessageException() (#643) - db.deleteMessage(txn, m.getId()); - return false; - } - // timestamp must be equal to timestamp of NEW_MEMBER message - if (timestamp != newMemberMeta.getLong(KEY_TIMESTAMP)) { - // FIXME throw new InvalidMessageException() (#643) - db.deleteMessage(txn, m.getId()); - return false; - } - // NEW_MEMBER must have same member_name and member_public_key - if (!Arrays.equals(meta.getRaw(KEY_MEMBER_ID), - newMemberMeta.getRaw(KEY_MEMBER_ID))) { - // FIXME throw new InvalidMessageException() (#643) - db.deleteMessage(txn, m.getId()); - return false; - } addMember(txn, m.getGroupId(), getAuthor(meta)); trackIncomingMessage(txn, m); return true; diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java index 49c0714c5c..2c60d72073 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java @@ -1,6 +1,7 @@ package org.briarproject.privategroup; import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.ContactGroupFactory; import org.briarproject.api.contact.ContactManager; import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.identity.AuthorFactory; @@ -58,14 +59,16 @@ public class PrivateGroupModule { @Provides @Singleton GroupMessageValidator provideGroupMessageValidator( + ContactGroupFactory contactGroupFactory, PrivateGroupFactory groupFactory, ValidationManager validationManager, ClientHelper clientHelper, MetadataEncoder metadataEncoder, Clock clock, - AuthorFactory authorFactory) { + AuthorFactory authorFactory, + GroupInvitationManager groupInvitationManager) { GroupMessageValidator validator = new GroupMessageValidator( - groupFactory, clientHelper, metadataEncoder, clock, - authorFactory); + contactGroupFactory, groupFactory, clientHelper, + metadataEncoder, clock, authorFactory, groupInvitationManager); validationManager.registerMessageValidator( PrivateGroupManagerImpl.CLIENT_ID, validator); -- GitLab