mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-15 20:29:52 +01:00
Merge branch '708-private-group-remove-new-member-announcement' into 'master'
Remove new member announcement and add signature to join message See merge request !384
This commit is contained in:
@@ -2,12 +2,15 @@ package org.briarproject;
|
|||||||
|
|
||||||
import net.jodah.concurrentunit.Waiter;
|
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.clients.MessageTracker.GroupCount;
|
||||||
import org.briarproject.api.contact.ContactId;
|
import org.briarproject.api.contact.ContactId;
|
||||||
import org.briarproject.api.contact.ContactManager;
|
import org.briarproject.api.contact.ContactManager;
|
||||||
import org.briarproject.api.crypto.CryptoComponent;
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.api.crypto.KeyPair;
|
import org.briarproject.api.crypto.KeyPair;
|
||||||
import org.briarproject.api.crypto.SecretKey;
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.db.DbException;
|
import org.briarproject.api.db.DbException;
|
||||||
import org.briarproject.api.db.Transaction;
|
import org.briarproject.api.db.Transaction;
|
||||||
import org.briarproject.api.event.Event;
|
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.PrivateGroup;
|
||||||
import org.briarproject.api.privategroup.PrivateGroupFactory;
|
import org.briarproject.api.privategroup.PrivateGroupFactory;
|
||||||
import org.briarproject.api.privategroup.PrivateGroupManager;
|
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.GroupId;
|
||||||
import org.briarproject.api.sync.MessageId;
|
import org.briarproject.api.sync.MessageId;
|
||||||
import org.briarproject.api.sync.SyncSession;
|
import org.briarproject.api.sync.SyncSession;
|
||||||
@@ -53,6 +58,7 @@ import java.util.logging.Logger;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.TestPluginsModule.MAX_LATENCY;
|
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.identity.Author.Status.VERIFIED;
|
||||||
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
||||||
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
|
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
|
||||||
@@ -72,18 +78,23 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
|||||||
private LocalAuthor author0, author1;
|
private LocalAuthor author0, author1;
|
||||||
private PrivateGroup privateGroup0;
|
private PrivateGroup privateGroup0;
|
||||||
private GroupId groupId0;
|
private GroupId groupId0;
|
||||||
private GroupMessage newMemberMsg0;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Clock clock;
|
Clock clock;
|
||||||
@Inject
|
@Inject
|
||||||
AuthorFactory authorFactory;
|
AuthorFactory authorFactory;
|
||||||
@Inject
|
@Inject
|
||||||
|
ClientHelper clientHelper;
|
||||||
|
@Inject
|
||||||
CryptoComponent crypto;
|
CryptoComponent crypto;
|
||||||
@Inject
|
@Inject
|
||||||
|
ContactGroupFactory contactGroupFactory;
|
||||||
|
@Inject
|
||||||
PrivateGroupFactory privateGroupFactory;
|
PrivateGroupFactory privateGroupFactory;
|
||||||
@Inject
|
@Inject
|
||||||
GroupMessageFactory groupMessageFactory;
|
GroupMessageFactory groupMessageFactory;
|
||||||
|
@Inject
|
||||||
|
GroupInvitationManager groupInvitationManager;
|
||||||
|
|
||||||
// objects accessed from background threads need to be volatile
|
// objects accessed from background threads need to be volatile
|
||||||
private volatile Waiter validationWaiter;
|
private volatile Waiter validationWaiter;
|
||||||
@@ -222,20 +233,6 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
|||||||
|
|
||||||
// assert that message did not arrive
|
// assert that message did not arrive
|
||||||
assertEquals(2, groupManager1.getHeaders(groupId0).size());
|
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
|
@Test
|
||||||
@@ -318,20 +315,18 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWrongJoinMessages() throws Exception {
|
public void testWrongJoinMessages1() throws Exception {
|
||||||
addDefaultIdentities();
|
addDefaultIdentities();
|
||||||
addDefaultContacts();
|
addDefaultContacts();
|
||||||
listenToEvents();
|
listenToEvents();
|
||||||
|
|
||||||
// author0 joins privateGroup0 with later timestamp
|
// author0 joins privateGroup0 with wrong join message
|
||||||
long joinTime = clock.currentTimeMillis();
|
long joinTime = clock.currentTimeMillis();
|
||||||
GroupMessage newMemberMsg = groupMessageFactory
|
GroupMessage joinMsg0 = groupMessageFactory
|
||||||
.createNewMemberMessage(groupId0, joinTime, author0, author0);
|
.createJoinMessage(privateGroup0.getId(), joinTime, author0,
|
||||||
GroupMessage joinMsg = groupMessageFactory
|
joinTime, getRandomBytes(12));
|
||||||
.createJoinMessage(groupId0, joinTime + 1, author0,
|
groupManager0.addPrivateGroup(privateGroup0, joinMsg0);
|
||||||
newMemberMsg.getMessage().getId());
|
assertEquals(joinMsg0.getMessage().getId(),
|
||||||
groupManager0.addPrivateGroup(privateGroup0, newMemberMsg, joinMsg);
|
|
||||||
assertEquals(joinMsg.getMessage().getId(),
|
|
||||||
groupManager0.getPreviousMsgId(groupId0));
|
groupManager0.getPreviousMsgId(groupId0));
|
||||||
|
|
||||||
// make group visible to 1
|
// make group visible to 1
|
||||||
@@ -342,15 +337,92 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
|||||||
t0.getDatabaseComponent().commitTransaction(txn0);
|
t0.getDatabaseComponent().commitTransaction(txn0);
|
||||||
t0.getDatabaseComponent().endTransaction(txn0);
|
t0.getDatabaseComponent().endTransaction(txn0);
|
||||||
|
|
||||||
// author1 joins privateGroup0 and refers to wrong NEW_MEMBER message
|
// author1 joins privateGroup0 with wrong timestamp
|
||||||
joinMsg = groupMessageFactory
|
|
||||||
.createJoinMessage(groupId0, joinTime, author1,
|
|
||||||
newMemberMsg.getMessage().getId());
|
|
||||||
joinTime = clock.currentTimeMillis();
|
joinTime = clock.currentTimeMillis();
|
||||||
newMemberMsg = groupMessageFactory
|
long inviteTime = joinTime;
|
||||||
.createNewMemberMessage(groupId0, joinTime, author0, author1);
|
Group invitationGroup = contactGroupFactory
|
||||||
groupManager1.addPrivateGroup(privateGroup0, newMemberMsg, joinMsg);
|
.createContactGroup(groupInvitationManager.getClientId(),
|
||||||
assertEquals(joinMsg.getMessage().getId(),
|
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
|
||||||
|
Transaction txn1 = t1.getDatabaseComponent().startTransaction(false);
|
||||||
|
t1.getDatabaseComponent()
|
||||||
|
.setVisibleToContact(txn1, contactId0, privateGroup0.getId(),
|
||||||
|
true);
|
||||||
|
t1.getDatabaseComponent().commitTransaction(txn1);
|
||||||
|
t1.getDatabaseComponent().endTransaction(txn1);
|
||||||
|
|
||||||
|
// sync join messages
|
||||||
|
sync0To1();
|
||||||
|
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 testWrongJoinMessages2() throws Exception {
|
||||||
|
addDefaultIdentities();
|
||||||
|
addDefaultContacts();
|
||||||
|
listenToEvents();
|
||||||
|
|
||||||
|
// author0 joins privateGroup0 with wrong member's join message
|
||||||
|
long joinTime = clock.currentTimeMillis();
|
||||||
|
long inviteTime = joinTime - 1;
|
||||||
|
Group invitationGroup = contactGroupFactory
|
||||||
|
.createContactGroup(groupInvitationManager.getClientId(),
|
||||||
|
author0.getId(), author0.getId());
|
||||||
|
BdfList toSign = BdfList.of(0, inviteTime, invitationGroup.getId(),
|
||||||
|
privateGroup0.getId());
|
||||||
|
byte[] creatorSignature =
|
||||||
|
clientHelper.sign(toSign, author0.getPrivateKey());
|
||||||
|
// join message should not include invite time and creator's signature
|
||||||
|
GroupMessage joinMsg0 = groupMessageFactory
|
||||||
|
.createJoinMessage(privateGroup0.getId(), joinTime, author0,
|
||||||
|
inviteTime, creatorSignature);
|
||||||
|
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);
|
||||||
|
t0.getDatabaseComponent().commitTransaction(txn0);
|
||||||
|
t0.getDatabaseComponent().endTransaction(txn0);
|
||||||
|
|
||||||
|
// author1 joins privateGroup0 with wrong signature in join message
|
||||||
|
joinTime = clock.currentTimeMillis();
|
||||||
|
inviteTime = joinTime - 1;
|
||||||
|
invitationGroup = contactGroupFactory
|
||||||
|
.createContactGroup(groupInvitationManager.getClientId(),
|
||||||
|
author0.getId(), author1.getId());
|
||||||
|
toSign = BdfList.of(0, inviteTime, invitationGroup.getId(),
|
||||||
|
privateGroup0.getId());
|
||||||
|
// signature uses joiner's key, not creator's key
|
||||||
|
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));
|
groupManager1.getPreviousMsgId(groupId0));
|
||||||
|
|
||||||
// make group visible to 0
|
// make group visible to 0
|
||||||
@@ -363,14 +435,12 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
|||||||
|
|
||||||
// sync join messages
|
// sync join messages
|
||||||
sync0To1();
|
sync0To1();
|
||||||
deliveryWaiter.await(TIMEOUT, 1);
|
|
||||||
validationWaiter.await(TIMEOUT, 1);
|
validationWaiter.await(TIMEOUT, 1);
|
||||||
|
|
||||||
// assert that 0 never joined the group from 1's perspective
|
// assert that 0 never joined the group from 1's perspective
|
||||||
assertEquals(1, groupManager1.getHeaders(groupId0).size());
|
assertEquals(1, groupManager1.getHeaders(groupId0).size());
|
||||||
|
|
||||||
sync1To0();
|
sync1To0();
|
||||||
deliveryWaiter.await(TIMEOUT, 1);
|
|
||||||
validationWaiter.await(TIMEOUT, 1);
|
validationWaiter.await(TIMEOUT, 1);
|
||||||
|
|
||||||
// assert that 1 never joined the group from 0's perspective
|
// assert that 1 never joined the group from 0's perspective
|
||||||
@@ -452,14 +522,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
|||||||
private void addGroup() throws Exception {
|
private void addGroup() throws Exception {
|
||||||
// author0 joins privateGroup0
|
// author0 joins privateGroup0
|
||||||
long joinTime = clock.currentTimeMillis();
|
long joinTime = clock.currentTimeMillis();
|
||||||
newMemberMsg0 = groupMessageFactory
|
GroupMessage joinMsg0 = groupMessageFactory
|
||||||
.createNewMemberMessage(privateGroup0.getId(), joinTime,
|
.createJoinMessage(privateGroup0.getId(), joinTime, author0);
|
||||||
author0, author0);
|
groupManager0.addPrivateGroup(privateGroup0, joinMsg0);
|
||||||
GroupMessage joinMsg = groupMessageFactory
|
assertEquals(joinMsg0.getMessage().getId(),
|
||||||
.createJoinMessage(privateGroup0.getId(), joinTime, author0,
|
|
||||||
newMemberMsg0.getMessage().getId());
|
|
||||||
groupManager0.addPrivateGroup(privateGroup0, newMemberMsg0, joinMsg);
|
|
||||||
assertEquals(joinMsg.getMessage().getId(),
|
|
||||||
groupManager0.getPreviousMsgId(groupId0));
|
groupManager0.getPreviousMsgId(groupId0));
|
||||||
|
|
||||||
// make group visible to 1
|
// make group visible to 1
|
||||||
@@ -467,19 +533,24 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
|||||||
t0.getDatabaseComponent()
|
t0.getDatabaseComponent()
|
||||||
.setVisibleToContact(txn0, contactId1, privateGroup0.getId(),
|
.setVisibleToContact(txn0, contactId1, privateGroup0.getId(),
|
||||||
true);
|
true);
|
||||||
t0.getDatabaseComponent().commitTransaction(txn0);;
|
t0.getDatabaseComponent().commitTransaction(txn0);
|
||||||
t0.getDatabaseComponent().endTransaction(txn0);
|
t0.getDatabaseComponent().endTransaction(txn0);
|
||||||
|
|
||||||
// author1 joins privateGroup0
|
// author1 joins privateGroup0
|
||||||
joinTime = clock.currentTimeMillis();
|
joinTime = clock.currentTimeMillis();
|
||||||
GroupMessage newMemberMsg1 = groupMessageFactory
|
long inviteTime = joinTime - 1;
|
||||||
.createNewMemberMessage(privateGroup0.getId(), joinTime,
|
Group invitationGroup = contactGroupFactory
|
||||||
author0, author1);
|
.createContactGroup(groupInvitationManager.getClientId(),
|
||||||
joinMsg = groupMessageFactory
|
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,
|
.createJoinMessage(privateGroup0.getId(), joinTime, author1,
|
||||||
newMemberMsg1.getMessage().getId());
|
inviteTime, creatorSignature);
|
||||||
groupManager1.addPrivateGroup(privateGroup0, newMemberMsg1, joinMsg);
|
groupManager1.addPrivateGroup(privateGroup0, joinMsg1);
|
||||||
assertEquals(joinMsg.getMessage().getId(),
|
assertEquals(joinMsg1.getMessage().getId(),
|
||||||
groupManager1.getPreviousMsgId(groupId0));
|
groupManager1.getPreviousMsgId(groupId0));
|
||||||
|
|
||||||
// make group visible to 0
|
// make group visible to 0
|
||||||
@@ -492,9 +563,9 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
|
|||||||
|
|
||||||
// sync join messages
|
// sync join messages
|
||||||
sync0To1();
|
sync0To1();
|
||||||
deliveryWaiter.await(TIMEOUT, 2);
|
deliveryWaiter.await(TIMEOUT, 1);
|
||||||
sync1To0();
|
sync1To0();
|
||||||
deliveryWaiter.await(TIMEOUT, 2);
|
deliveryWaiter.await(TIMEOUT, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sync0To1() throws IOException, TimeoutException {
|
private void sync0To1() throws IOException, TimeoutException {
|
||||||
|
|||||||
@@ -82,29 +82,24 @@ public class CreateGroupControllerImpl extends DbControllerImpl
|
|||||||
LOG.info("Creating group...");
|
LOG.info("Creating group...");
|
||||||
PrivateGroup group =
|
PrivateGroup group =
|
||||||
groupFactory.createPrivateGroup(name, author);
|
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...");
|
LOG.info("Creating new join announcement...");
|
||||||
GroupMessage joinMsg = groupMessageFactory
|
GroupMessage joinMsg = groupMessageFactory
|
||||||
.createJoinMessage(group.getId(),
|
.createJoinMessage(group.getId(),
|
||||||
newMemberMsg.getMessage().getTimestamp(),
|
clock.currentTimeMillis(), author);
|
||||||
author, newMemberMsg.getMessage().getId());
|
storeGroup(group, joinMsg, handler);
|
||||||
storeGroup(group, newMemberMsg, joinMsg, handler);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeGroup(final PrivateGroup group,
|
private void storeGroup(final PrivateGroup group,
|
||||||
final GroupMessage newMemberMsg, final GroupMessage joinMsg,
|
final GroupMessage joinMsg,
|
||||||
final ResultExceptionHandler<GroupId, DbException> handler) {
|
final ResultExceptionHandler<GroupId, DbException> handler) {
|
||||||
runOnDbThread(new Runnable() {
|
runOnDbThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
LOG.info("Adding group to database...");
|
LOG.info("Adding group to database...");
|
||||||
try {
|
try {
|
||||||
groupManager.addPrivateGroup(group, newMemberMsg, joinMsg);
|
groupManager.addPrivateGroup(group, joinMsg);
|
||||||
handler.onResult(group.getId());
|
handler.onResult(group.getId());
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
if (LOG.isLoggable(WARNING))
|
if (LOG.isLoggable(WARNING))
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package org.briarproject.api.clients;
|
package org.briarproject.api.clients;
|
||||||
|
|
||||||
import org.briarproject.api.contact.Contact;
|
import org.briarproject.api.contact.Contact;
|
||||||
|
import org.briarproject.api.identity.AuthorId;
|
||||||
import org.briarproject.api.sync.ClientId;
|
import org.briarproject.api.sync.ClientId;
|
||||||
import org.briarproject.api.sync.Group;
|
import org.briarproject.api.sync.Group;
|
||||||
import org.briarproject.api.sync.GroupFactory;
|
|
||||||
|
|
||||||
public interface ContactGroupFactory {
|
public interface ContactGroupFactory {
|
||||||
|
|
||||||
@@ -13,4 +13,11 @@ public interface ContactGroupFactory {
|
|||||||
/** Creates a group for the given client to share with the given contact. */
|
/** Creates a group for the given client to share with the given contact. */
|
||||||
Group createContactGroup(ClientId clientId, Contact 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,33 +10,29 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
public interface GroupMessageFactory {
|
public interface GroupMessageFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new member announcement that contains the joiner's identity
|
* Creates a join announcement message for the creator of a group.
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
@CryptoExecutor
|
|
||||||
GroupMessage createNewMemberMessage(GroupId groupId, long timestamp,
|
|
||||||
LocalAuthor creator, Author member);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a join announcement message
|
|
||||||
* that depends on a previous new member announcement.
|
|
||||||
*
|
*
|
||||||
* @param groupId The ID of the Group that is being joined
|
* @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 timestamp Must be greater than the timestamp of the invitation message
|
||||||
* @param member Our own LocalAuthor
|
* @param creator The creator's LocalAuthor
|
||||||
* @param newMemberId The MessageId of the new member message
|
|
||||||
*/
|
*/
|
||||||
@CryptoExecutor
|
@CryptoExecutor
|
||||||
GroupMessage createJoinMessage(GroupId groupId, long timestamp,
|
GroupMessage createJoinMessage(GroupId groupId, long timestamp,
|
||||||
LocalAuthor member, MessageId newMemberId);
|
LocalAuthor creator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a join announcement message for a joining member.
|
||||||
|
*
|
||||||
|
* @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, long inviteTimestamp, byte[] creatorSignature);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a group message
|
* Creates a group message
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package org.briarproject.api.privategroup;
|
package org.briarproject.api.privategroup;
|
||||||
|
|
||||||
public enum MessageType {
|
public enum MessageType {
|
||||||
NEW_MEMBER(0),
|
JOIN(0),
|
||||||
JOIN(1),
|
POST(1);
|
||||||
POST(2);
|
|
||||||
|
|
||||||
int value;
|
int value;
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,10 @@ public interface PrivateGroupManager extends MessageTracker {
|
|||||||
* Adds a new private group and joins it.
|
* Adds a new private group and joins it.
|
||||||
*
|
*
|
||||||
* @param group The private group to add
|
* @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
|
* @param joinMsg The creator's own join message
|
||||||
*/
|
*/
|
||||||
void addPrivateGroup(PrivateGroup group, GroupMessage newMemberMsg,
|
void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg)
|
||||||
GroupMessage joinMsg) throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/** Removes a dissolved private group. */
|
/** Removes a dissolved private group. */
|
||||||
void removePrivateGroup(GroupId g) throws DbException;
|
void removePrivateGroup(GroupId g) throws DbException;
|
||||||
|
|||||||
@@ -41,6 +41,13 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
|
|||||||
return groupFactory.createGroup(clientId, descriptor);
|
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) {
|
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {
|
||||||
try {
|
try {
|
||||||
if (Bytes.COMPARATOR.compare(local, remote) < 0)
|
if (Bytes.COMPARATOR.compare(local, remote) < 0)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.briarproject.privategroup;
|
|||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.clients.ClientHelper;
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.data.BdfList;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.identity.Author;
|
|
||||||
import org.briarproject.api.identity.LocalAuthor;
|
import org.briarproject.api.identity.LocalAuthor;
|
||||||
import org.briarproject.api.nullsafety.NotNullByDefault;
|
import org.briarproject.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.api.privategroup.GroupMessage;
|
import org.briarproject.api.privategroup.GroupMessage;
|
||||||
@@ -18,7 +17,6 @@ import java.security.GeneralSecurityException;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.api.privategroup.MessageType.JOIN;
|
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.MessageType.POST;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -32,45 +30,34 @@ class GroupMessageFactoryImpl implements GroupMessageFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GroupMessage createNewMemberMessage(GroupId groupId, long timestamp,
|
public GroupMessage createJoinMessage(GroupId groupId, long timestamp,
|
||||||
LocalAuthor creator, Author member) {
|
LocalAuthor creator) {
|
||||||
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
|
return createJoinMessage(groupId, timestamp, creator, null);
|
||||||
BdfList body =
|
|
||||||
BdfList.of(type, member.getName(),
|
|
||||||
member.getPublicKey(), signature);
|
|
||||||
Message m = clientHelper.createMessage(groupId, timestamp, body);
|
|
||||||
|
|
||||||
return new GroupMessage(m, null, member);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GroupMessage createJoinMessage(GroupId groupId, long timestamp,
|
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 {
|
try {
|
||||||
// Generate the signature
|
// Generate the signature
|
||||||
int type = JOIN.getInt();
|
int type = JOIN.getInt();
|
||||||
BdfList toSign = BdfList.of(groupId, timestamp, type,
|
BdfList toSign = BdfList.of(groupId, timestamp, type,
|
||||||
member.getName(), member.getPublicKey(), newMemberId);
|
member.getName(), member.getPublicKey(), invite);
|
||||||
byte[] signature =
|
byte[] memberSignature =
|
||||||
clientHelper.sign(toSign, member.getPrivateKey());
|
clientHelper.sign(toSign, member.getPrivateKey());
|
||||||
|
|
||||||
// Compose the message
|
// Compose the message
|
||||||
BdfList body =
|
BdfList body =
|
||||||
BdfList.of(type, member.getName(),
|
BdfList.of(type, member.getName(),
|
||||||
member.getPublicKey(), newMemberId, signature);
|
member.getPublicKey(), invite, memberSignature);
|
||||||
Message m = clientHelper.createMessage(groupId, timestamp, body);
|
Message m = clientHelper.createMessage(groupId, timestamp, body);
|
||||||
|
|
||||||
return new GroupMessage(m, null, member);
|
return new GroupMessage(m, null, member);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.privategroup;
|
|||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.clients.BdfMessageContext;
|
import org.briarproject.api.clients.BdfMessageContext;
|
||||||
import org.briarproject.api.clients.ClientHelper;
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
|
import org.briarproject.api.clients.ContactGroupFactory;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfList;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
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.MessageType;
|
||||||
import org.briarproject.api.privategroup.PrivateGroup;
|
import org.briarproject.api.privategroup.PrivateGroup;
|
||||||
import org.briarproject.api.privategroup.PrivateGroupFactory;
|
import org.briarproject.api.privategroup.PrivateGroupFactory;
|
||||||
|
import org.briarproject.api.privategroup.invitation.GroupInvitationManager;
|
||||||
import org.briarproject.api.sync.Group;
|
import org.briarproject.api.sync.Group;
|
||||||
import org.briarproject.api.sync.InvalidMessageException;
|
import org.briarproject.api.sync.InvalidMessageException;
|
||||||
import org.briarproject.api.sync.Message;
|
import org.briarproject.api.sync.Message;
|
||||||
@@ -21,19 +23,16 @@ import org.briarproject.clients.BdfMessageValidator;
|
|||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
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_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_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.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||||
import static org.briarproject.api.privategroup.MessageType.JOIN;
|
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.MessageType.POST;
|
||||||
import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
|
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_ID;
|
||||||
import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME;
|
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_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_PARENT_MSG_ID;
|
||||||
import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID;
|
import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID;
|
||||||
import static org.briarproject.privategroup.Constants.KEY_READ;
|
import static org.briarproject.privategroup.Constants.KEY_READ;
|
||||||
@@ -42,52 +41,50 @@ import static org.briarproject.privategroup.Constants.KEY_TYPE;
|
|||||||
|
|
||||||
class GroupMessageValidator extends BdfMessageValidator {
|
class GroupMessageValidator extends BdfMessageValidator {
|
||||||
|
|
||||||
|
private final ContactGroupFactory contactGroupFactory;
|
||||||
private final PrivateGroupFactory groupFactory;
|
private final PrivateGroupFactory groupFactory;
|
||||||
private final AuthorFactory authorFactory;
|
private final AuthorFactory authorFactory;
|
||||||
|
private final GroupInvitationManager groupInvitationManager; // TODO remove
|
||||||
|
|
||||||
GroupMessageValidator(PrivateGroupFactory groupFactory,
|
GroupMessageValidator(ContactGroupFactory contactGroupFactory,
|
||||||
|
PrivateGroupFactory groupFactory,
|
||||||
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
||||||
Clock clock, AuthorFactory authorFactory) {
|
Clock clock, AuthorFactory authorFactory,
|
||||||
|
GroupInvitationManager groupInvitationManager) {
|
||||||
super(clientHelper, metadataEncoder, clock);
|
super(clientHelper, metadataEncoder, clock);
|
||||||
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
this.groupFactory = groupFactory;
|
this.groupFactory = groupFactory;
|
||||||
this.authorFactory = authorFactory;
|
this.authorFactory = authorFactory;
|
||||||
|
this.groupInvitationManager = groupInvitationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||||
BdfList body) throws InvalidMessageException, FormatException {
|
BdfList body) throws InvalidMessageException, FormatException {
|
||||||
|
|
||||||
checkSize(body, 4, 7);
|
checkSize(body, 5, 7);
|
||||||
|
|
||||||
// message type (int)
|
// message type (int)
|
||||||
int type = body.getLong(0).intValue();
|
int type = body.getLong(0).intValue();
|
||||||
body.removeElementAt(0);
|
|
||||||
|
|
||||||
// member_name (string)
|
// member_name (string)
|
||||||
String memberName = body.getString(0);
|
String memberName = body.getString(1);
|
||||||
checkLength(memberName, 1, MAX_AUTHOR_NAME_LENGTH);
|
checkLength(memberName, 1, MAX_AUTHOR_NAME_LENGTH);
|
||||||
|
|
||||||
// member_public_key (raw)
|
// member_public_key (raw)
|
||||||
byte[] memberPublicKey = body.getRaw(1);
|
byte[] memberPublicKey = body.getRaw(2);
|
||||||
checkLength(memberPublicKey, 1, MAX_PUBLIC_KEY_LENGTH);
|
checkLength(memberPublicKey, 1, MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
|
||||||
|
Author member = authorFactory.createAuthor(memberName, memberPublicKey);
|
||||||
BdfMessageContext c;
|
BdfMessageContext c;
|
||||||
switch (MessageType.valueOf(type)) {
|
switch (MessageType.valueOf(type)) {
|
||||||
case NEW_MEMBER:
|
|
||||||
c = validateNewMember(m, g, body, memberName,
|
|
||||||
memberPublicKey);
|
|
||||||
addMessageMetadata(c, memberName, memberPublicKey,
|
|
||||||
m.getTimestamp());
|
|
||||||
break;
|
|
||||||
case JOIN:
|
case JOIN:
|
||||||
c = validateJoin(m, g, body, memberName, memberPublicKey);
|
c = validateJoin(m, g, body, member);
|
||||||
addMessageMetadata(c, memberName, memberPublicKey,
|
addMessageMetadata(c, member, m.getTimestamp());
|
||||||
m.getTimestamp());
|
|
||||||
break;
|
break;
|
||||||
case POST:
|
case POST:
|
||||||
c = validatePost(m, g, body, memberName, memberPublicKey);
|
c = validatePost(m, g, body, member);
|
||||||
addMessageMetadata(c, memberName, memberPublicKey,
|
addMessageMetadata(c, member, m.getTimestamp());
|
||||||
m.getTimestamp());
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new InvalidMessageException("Unknown Message Type");
|
throw new InvalidMessageException("Unknown Message Type");
|
||||||
@@ -96,26 +93,67 @@ class GroupMessageValidator extends BdfMessageValidator {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BdfMessageContext validateNewMember(Message m, Group g,
|
private BdfMessageContext validateJoin(Message m, Group g, BdfList body,
|
||||||
BdfList body, String memberName, byte[] memberPublicKey)
|
Author member)
|
||||||
throws InvalidMessageException, FormatException {
|
throws InvalidMessageException, FormatException {
|
||||||
|
|
||||||
// The content is a BDF list with three elements
|
// The content is a BDF list with five elements
|
||||||
checkSize(body, 3);
|
checkSize(body, 5);
|
||||||
|
PrivateGroup pg = groupFactory.parsePrivateGroup(g);
|
||||||
|
|
||||||
// signature (raw)
|
// invite is null if the member is the creator of the private group
|
||||||
// signature with the creator's private key over a list with 4 elements
|
BdfList invite = body.getOptionalList(3);
|
||||||
byte[] signature = body.getRaw(2);
|
if (invite == null) {
|
||||||
checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
|
if (!member.equals(pg.getAuthor()))
|
||||||
|
throw new InvalidMessageException();
|
||||||
|
} else {
|
||||||
|
if (member.equals(pg.getAuthor()))
|
||||||
|
throw new InvalidMessageException();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Verify Signature
|
||||||
BdfList signed =
|
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), JOIN.getInt(),
|
||||||
BdfList.of(g.getId(), m.getTimestamp(), NEW_MEMBER.getInt(),
|
member.getName(), member.getPublicKey(), invite);
|
||||||
memberName, memberPublicKey);
|
|
||||||
PrivateGroup group = groupFactory.parsePrivateGroup(g);
|
|
||||||
byte[] creatorPublicKey = group.getAuthor().getPublicKey();
|
|
||||||
try {
|
try {
|
||||||
clientHelper.verifySignature(signature, creatorPublicKey, signed);
|
clientHelper.verifySignature(memberSignature, member.getPublicKey(),
|
||||||
|
signed);
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
@@ -125,75 +163,39 @@ class GroupMessageValidator extends BdfMessageValidator {
|
|||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BdfMessageContext validateJoin(Message m, Group g, BdfList body,
|
|
||||||
String memberName, byte[] memberPublicKey)
|
|
||||||
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[] 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);
|
|
||||||
|
|
||||||
// Verify Signature
|
|
||||||
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), JOIN.getInt(),
|
|
||||||
memberName, memberPublicKey, newMemberId);
|
|
||||||
try {
|
|
||||||
clientHelper.verifySignature(signature, memberPublicKey, 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
|
|
||||||
BdfDictionary meta = new BdfDictionary();
|
|
||||||
meta.put(KEY_NEW_MEMBER_MSG_ID, newMemberId);
|
|
||||||
return new BdfMessageContext(meta, dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BdfMessageContext validatePost(Message m, Group g, BdfList body,
|
private BdfMessageContext validatePost(Message m, Group g, BdfList body,
|
||||||
String memberName, byte[] memberPublicKey)
|
Author member)
|
||||||
throws InvalidMessageException, FormatException {
|
throws InvalidMessageException, FormatException {
|
||||||
|
|
||||||
// The content is a BDF list with six elements
|
// The content is a BDF list with seven elements
|
||||||
checkSize(body, 6);
|
checkSize(body, 7);
|
||||||
|
|
||||||
// parent_id (raw or null)
|
// parent_id (raw or null)
|
||||||
// the identifier of the post to which this is a reply, if any
|
// 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);
|
checkLength(parentId, MessageId.LENGTH);
|
||||||
|
|
||||||
// previous_message_id (raw)
|
// previous_message_id (raw)
|
||||||
// the identifier of the member's previous post or join message
|
// 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);
|
checkLength(previousMessageId, MessageId.LENGTH);
|
||||||
|
|
||||||
// content (string)
|
// content (string)
|
||||||
String content = body.getString(4);
|
String content = body.getString(5);
|
||||||
checkLength(content, 0, MAX_GROUP_POST_BODY_LENGTH);
|
checkLength(content, 0, MAX_GROUP_POST_BODY_LENGTH);
|
||||||
|
|
||||||
// signature (raw)
|
// signature (raw)
|
||||||
// a signature with the member's private key over a list with 7 elements
|
// 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);
|
checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
|
||||||
|
|
||||||
// Verify Signature
|
// Verify Signature
|
||||||
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), POST.getInt(),
|
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), POST.getInt(),
|
||||||
memberName, memberPublicKey, parentId, previousMessageId,
|
member.getName(), member.getPublicKey(), parentId,
|
||||||
content);
|
previousMessageId, content);
|
||||||
try {
|
try {
|
||||||
clientHelper.verifySignature(signature, memberPublicKey, signed);
|
clientHelper
|
||||||
|
.verifySignature(signature, member.getPublicKey(), signed);
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
@@ -211,14 +213,13 @@ class GroupMessageValidator extends BdfMessageValidator {
|
|||||||
return new BdfMessageContext(meta, dependencies);
|
return new BdfMessageContext(meta, dependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMessageMetadata(BdfMessageContext c, String authorName,
|
private void addMessageMetadata(BdfMessageContext c, Author member,
|
||||||
byte[] pubKey, long time) {
|
long time) {
|
||||||
c.getDictionary().put(KEY_TIMESTAMP, time);
|
c.getDictionary().put(KEY_TIMESTAMP, time);
|
||||||
c.getDictionary().put(KEY_READ, false);
|
c.getDictionary().put(KEY_READ, false);
|
||||||
Author a = authorFactory.createAuthor(authorName, pubKey);
|
c.getDictionary().put(KEY_MEMBER_ID, member.getId());
|
||||||
c.getDictionary().put(KEY_MEMBER_ID, a.getId());
|
c.getDictionary().put(KEY_MEMBER_NAME, member.getName());
|
||||||
c.getDictionary().put(KEY_MEMBER_NAME, authorName);
|
c.getDictionary().put(KEY_MEMBER_PUBLIC_KEY, member.getPublicKey());
|
||||||
c.getDictionary().put(KEY_MEMBER_PUBLIC_KEY, pubKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.UNVERIFIED;
|
||||||
import static org.briarproject.api.identity.Author.Status.VERIFIED;
|
import static org.briarproject.api.identity.Author.Status.VERIFIED;
|
||||||
import static org.briarproject.api.privategroup.MessageType.JOIN;
|
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.MessageType.POST;
|
||||||
import static org.briarproject.privategroup.Constants.KEY_DISSOLVED;
|
import static org.briarproject.privategroup.Constants.KEY_DISSOLVED;
|
||||||
import static org.briarproject.privategroup.Constants.KEY_MEMBERS;
|
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_ID;
|
||||||
import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME;
|
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_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_PARENT_MSG_ID;
|
||||||
import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID;
|
import static org.briarproject.privategroup.Constants.KEY_PREVIOUS_MSG_ID;
|
||||||
import static org.briarproject.privategroup.Constants.KEY_READ;
|
import static org.briarproject.privategroup.Constants.KEY_READ;
|
||||||
@@ -95,8 +93,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addPrivateGroup(PrivateGroup group,
|
public void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg)
|
||||||
GroupMessage newMemberMsg, GroupMessage joinMsg)
|
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
@@ -106,7 +103,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
|||||||
new BdfEntry(KEY_DISSOLVED, false)
|
new BdfEntry(KEY_DISSOLVED, false)
|
||||||
);
|
);
|
||||||
clientHelper.mergeGroupMetadata(txn, group.getId(), meta);
|
clientHelper.mergeGroupMetadata(txn, group.getId(), meta);
|
||||||
announceNewMember(txn, newMemberMsg);
|
|
||||||
joinPrivateGroup(txn, joinMsg);
|
joinPrivateGroup(txn, joinMsg);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} catch (FormatException e) {
|
} 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)
|
private void joinPrivateGroup(Transaction txn, GroupMessage m)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
@@ -315,8 +303,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
|||||||
// get all authors we need to get the status for
|
// get all authors we need to get the status for
|
||||||
Set<AuthorId> authors = new HashSet<AuthorId>();
|
Set<AuthorId> authors = new HashSet<AuthorId>();
|
||||||
for (BdfDictionary meta : metadata.values()) {
|
for (BdfDictionary meta : metadata.values()) {
|
||||||
if (meta.getLong(KEY_TYPE) == NEW_MEMBER.getInt())
|
|
||||||
continue;
|
|
||||||
byte[] idBytes = meta.getRaw(KEY_MEMBER_ID);
|
byte[] idBytes = meta.getRaw(KEY_MEMBER_ID);
|
||||||
authors.add(new AuthorId(idBytes));
|
authors.add(new AuthorId(idBytes));
|
||||||
}
|
}
|
||||||
@@ -328,8 +314,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
|||||||
// Parse the metadata
|
// Parse the metadata
|
||||||
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
||||||
BdfDictionary meta = entry.getValue();
|
BdfDictionary meta = entry.getValue();
|
||||||
if (meta.getLong(KEY_TYPE) == NEW_MEMBER.getInt())
|
|
||||||
continue;
|
|
||||||
headers.add(getGroupMessageHeader(txn, g, entry.getKey(), meta,
|
headers.add(getGroupMessageHeader(txn, g, entry.getKey(), meta,
|
||||||
statuses));
|
statuses));
|
||||||
}
|
}
|
||||||
@@ -434,36 +418,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
|
|||||||
MessageType type =
|
MessageType type =
|
||||||
MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
|
MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case NEW_MEMBER:
|
|
||||||
// don't track incoming message, because it won't show in the UI
|
|
||||||
return true;
|
|
||||||
case JOIN:
|
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));
|
addMember(txn, m.getGroupId(), getAuthor(meta));
|
||||||
trackIncomingMessage(txn, m);
|
trackIncomingMessage(txn, m);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.privategroup;
|
package org.briarproject.privategroup;
|
||||||
|
|
||||||
import org.briarproject.api.clients.ClientHelper;
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
|
import org.briarproject.api.clients.ContactGroupFactory;
|
||||||
import org.briarproject.api.contact.ContactManager;
|
import org.briarproject.api.contact.ContactManager;
|
||||||
import org.briarproject.api.data.MetadataEncoder;
|
import org.briarproject.api.data.MetadataEncoder;
|
||||||
import org.briarproject.api.identity.AuthorFactory;
|
import org.briarproject.api.identity.AuthorFactory;
|
||||||
@@ -58,14 +59,16 @@ public class PrivateGroupModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
GroupMessageValidator provideGroupMessageValidator(
|
GroupMessageValidator provideGroupMessageValidator(
|
||||||
|
ContactGroupFactory contactGroupFactory,
|
||||||
PrivateGroupFactory groupFactory,
|
PrivateGroupFactory groupFactory,
|
||||||
ValidationManager validationManager, ClientHelper clientHelper,
|
ValidationManager validationManager, ClientHelper clientHelper,
|
||||||
MetadataEncoder metadataEncoder, Clock clock,
|
MetadataEncoder metadataEncoder, Clock clock,
|
||||||
AuthorFactory authorFactory) {
|
AuthorFactory authorFactory,
|
||||||
|
GroupInvitationManager groupInvitationManager) {
|
||||||
|
|
||||||
GroupMessageValidator validator = new GroupMessageValidator(
|
GroupMessageValidator validator = new GroupMessageValidator(
|
||||||
groupFactory, clientHelper, metadataEncoder, clock,
|
contactGroupFactory, groupFactory, clientHelper,
|
||||||
authorFactory);
|
metadataEncoder, clock, authorFactory, groupInvitationManager);
|
||||||
validationManager.registerMessageValidator(
|
validationManager.registerMessageValidator(
|
||||||
PrivateGroupManagerImpl.CLIENT_ID, validator);
|
PrivateGroupManagerImpl.CLIENT_ID, validator);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user