Merge branch '627-tests-for-introduction-security-properties' into 'master'

Add one introduction test for modified response message

This is only the first part of #627, but I am putting it up for review already, since the second part will be very similar and issues found here will likely apply to both.

See merge request !306
This commit is contained in:
akwizgran
2016-09-07 13:08:34 +00:00
2 changed files with 99 additions and 96 deletions

View File

@@ -1,7 +1,10 @@
package org.briarproject; package org.briarproject;
import android.support.annotation.Nullable;
import net.jodah.concurrentunit.Waiter; import net.jodah.concurrentunit.Waiter;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.SessionId; import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
@@ -35,6 +38,7 @@ import org.briarproject.api.properties.TransportProperties;
import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.properties.TransportPropertyManager;
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.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;
import org.briarproject.api.sync.SyncSessionFactory; import org.briarproject.api.sync.SyncSessionFactory;
@@ -59,6 +63,7 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -69,22 +74,15 @@ import javax.inject.Inject;
import static org.briarproject.TestPluginsModule.MAX_LATENCY; import static org.briarproject.TestPluginsModule.MAX_LATENCY;
import static org.briarproject.TestPluginsModule.TRANSPORT_ID; import static org.briarproject.TestPluginsModule.TRANSPORT_ID;
import static org.briarproject.api.clients.MessageQueueManager.QUEUE_STATE_KEY;
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.introduction.IntroductionConstants.ACCEPT;
import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID; import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.api.introduction.IntroductionConstants.MAC_LENGTH;
import static org.briarproject.api.introduction.IntroductionConstants.NAME; import static org.briarproject.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY; import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID; import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.api.introduction.IntroductionConstants.STATE; import static org.briarproject.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT; import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE; import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
@@ -95,11 +93,13 @@ import static org.junit.Assert.assertTrue;
public class IntroductionIntegrationTest extends BriarTestCase { public class IntroductionIntegrationTest extends BriarTestCase {
private LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2; private LifecycleManager lifecycleManager0, lifecycleManager1,
lifecycleManager2;
private SyncSessionFactory sync0, sync1, sync2; private SyncSessionFactory sync0, sync1, sync2;
private ContactManager contactManager0, contactManager1, contactManager2; private ContactManager contactManager0, contactManager1, contactManager2;
private ContactId contactId0, contactId1, contactId2; private ContactId contactId0, contactId1, contactId2;
private IdentityManager identityManager0, identityManager1, identityManager2; private IdentityManager identityManager0, identityManager1,
identityManager2;
private LocalAuthor author0, author1, author2; private LocalAuthor author0, author1, author2;
@Inject @Inject
@@ -108,6 +108,8 @@ public class IntroductionIntegrationTest extends BriarTestCase {
CryptoComponent crypto; CryptoComponent crypto;
@Inject @Inject
AuthorFactory authorFactory; AuthorFactory authorFactory;
@Inject
IntroductionGroupFactory introductionGroupFactory;
// objects accessed from background threads need to be volatile // objects accessed from background threads need to be volatile
private volatile IntroductionManager introductionManager0; private volatile IntroductionManager introductionManager0;
@@ -834,7 +836,7 @@ public class IntroductionIntegrationTest extends BriarTestCase {
} }
@Test @Test
public void testFakeResponse() throws Exception { public void testModifiedResponse() throws Exception {
startLifecycles(); startLifecycles();
try { try {
addDefaultIdentities(); addDefaultIdentities();
@@ -856,122 +858,106 @@ public class IntroductionIntegrationTest extends BriarTestCase {
introductionManager0 introductionManager0
.makeIntroduction(introducee1, introducee2, "Hi!", time); .makeIntroduction(introducee1, introducee2, "Hi!", time);
// sync first request message // sync request messages
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1"); deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
eventWaiter.await(TIMEOUT, 1); deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
assertTrue(listener1.requestReceived); eventWaiter.await(TIMEOUT, 2);
// sync first response // sync first response
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0"); deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response1Received);
// get SessionId // get response to be forwarded
List<IntroductionMessage> list = new ArrayList<>( MessageId responseId = null;
introductionManager1.getIntroductionMessages(contactId0)); BdfDictionary response = null;
assertEquals(2, list.size()); Group g2 = introductionGroupFactory
assertTrue(list.get(0) instanceof IntroductionRequest); .createIntroductionGroup(introducee2);
IntroductionRequest msg = (IntroductionRequest) list.get(0); ClientHelper clientHelper0 = t0.getClientHelper();
SessionId sessionId = msg.getSessionId(); Map<MessageId, BdfDictionary> map =
clientHelper0.getMessageMetadataAsDictionary(g2.getId());
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
if (entry.getValue().getLong(TYPE) == TYPE_RESPONSE) {
responseId = entry.getKey();
response = entry.getValue();
}
}
assertTrue(responseId != null && response != null);
// get contact group // adapt outgoing message queue to removed message
IntroductionGroupFactory groupFactory = decreaseOutgoingMessageCounter(clientHelper0, g2.getId(), 1);
t0.getIntroductionGroupFactory();
Group group = groupFactory.createIntroductionGroup(introducee1);
// get data for contact2 // modify response by changing transport properties
long timestamp = clock.currentTimeMillis(); BdfDictionary tp = response.getDictionary(TRANSPORT);
KeyPair eKeyPair = crypto.generateAgreementKeyPair(); tp.put("fakeId", BdfDictionary.of(new BdfEntry("fake", "fake")));
byte[] ePublicKey = eKeyPair.getPublic().getEncoded(); response.put(TRANSPORT, tp);
TransportProperties tp = new TransportProperties(
Collections.singletonMap("key", "value"));
BdfDictionary tpDict = BdfDictionary.of(new BdfEntry("fake", tp));
// create a fake response // replace original response with modified one
BdfDictionary d = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_RESPONSE),
new BdfEntry(SESSION_ID, sessionId),
new BdfEntry(GROUP_ID, group.getId()),
new BdfEntry(ACCEPT, true),
new BdfEntry(TIME, timestamp),
new BdfEntry(E_PUBLIC_KEY, ePublicKey),
new BdfEntry(TRANSPORT, tpDict)
);
// add the message to the queue
DatabaseComponent db0 = t0.getDatabaseComponent();
MessageSender sender0 = t0.getMessageSender(); MessageSender sender0 = t0.getMessageSender();
DatabaseComponent db0 = t0.getDatabaseComponent();
Transaction txn = db0.startTransaction(false); Transaction txn = db0.startTransaction(false);
try { try {
sender0.sendMessage(txn, d); db0.deleteMessage(txn, responseId);
sender0.sendMessage(txn, response);
txn.setComplete(); txn.setComplete();
} finally { } finally {
db0.endTransaction(txn); db0.endTransaction(txn);
} }
// send the fake response // sync second response
deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
eventWaiter.await(TIMEOUT, 1);
// sync forwarded responses to introducees
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
// sync first ACK and its forward
deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1"); deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
// fake session state for introducer, so she doesn't abort // sync second ACK and forward it
ClientHelper clientHelper0 = t0.getClientHelper();
BdfDictionary state =
clientHelper0.getMessageMetadataAsDictionary(sessionId);
state.put(STATE, IntroducerProtocolState.AWAIT_ACKS.getValue());
clientHelper0.mergeMessageMetadata(sessionId, state);
// sync back the ACK
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0"); deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
// create a fake ACK // introducee2 should have detected the fake now
// TODO do we need to actually calculate a MAC and signature here? // and deleted introducee1 again
byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH); Collection<Contact> contacts2;
byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH); DatabaseComponent db2 = t2.getDatabaseComponent();
d = BdfDictionary.of( txn = db2.startTransaction(true);
new BdfEntry(TYPE, TYPE_ACK),
new BdfEntry(SESSION_ID, sessionId),
new BdfEntry(GROUP_ID, group.getId()),
new BdfEntry(MAC, mac),
new BdfEntry(SIGNATURE, sig)
);
// add the fake ACK to the message queue
txn = db0.startTransaction(false);
try { try {
sender0.sendMessage(txn, d); contacts2 = db2.getContacts(txn);
txn.setComplete(); txn.setComplete();
} finally { } finally {
db0.endTransaction(txn); db2.endTransaction(txn);
} }
assertEquals(1, contacts2.size());
// make sure the contact was already added (as inactive) // sync abort message to introducer
deliverMessage(sync2, contactId2, sync0, contactId0, "2 to 0");
// ensure introducer got the abort
SessionId sessionId = new SessionId(response.getRaw(SESSION_ID));
BdfDictionary state =
clientHelper0.getMessageMetadataAsDictionary(sessionId);
assertEquals(IntroducerProtocolState.ERROR.getValue(),
state.getLong(STATE).intValue());
// sync abort messages to introducees
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
// although aborted, introducee1 keeps the contact,
// so introducer can not make contacts disappear by sending abort
Collection<Contact> contacts1;
DatabaseComponent db1 = t1.getDatabaseComponent(); DatabaseComponent db1 = t1.getDatabaseComponent();
txn = db1.startTransaction(true); txn = db1.startTransaction(true);
try { try {
assertEquals(2, db1.getContacts(txn).size()); contacts1 = db1.getContacts(txn);
txn.setComplete(); txn.setComplete();
} finally { } finally {
db1.endTransaction(txn); db1.endTransaction(txn);
} }
assertEquals(2, contacts1.size());
// send the fake ACK
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
// make sure session was aborted and contact deleted again
txn = db1.startTransaction(true);
try {
assertEquals(1, db1.getContacts(txn).size());
txn.setComplete();
} finally {
db1.endTransaction(txn);
}
// there should now be an abort message to sync back
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
// ensure introducer got the abort
state = clientHelper0.getMessageMetadataAsDictionary(sessionId);
assertEquals(IntroducerProtocolState.ERROR.getValue(),
state.getLong(STATE).intValue());
} finally { } finally {
stopLifecycles(); stopLifecycles();
} }
@@ -1067,7 +1053,7 @@ public class IntroductionIntegrationTest extends BriarTestCase {
} }
private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId, private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
SyncSessionFactory toSync, ContactId toId, String debug) SyncSessionFactory toSync, ContactId toId, @Nullable String debug)
throws IOException, TimeoutException { throws IOException, TimeoutException {
if (debug != null) LOG.info("TEST: Sending message from " + debug); if (debug != null) LOG.info("TEST: Sending message from " + debug);
@@ -1214,6 +1200,16 @@ public class IntroductionIntegrationTest extends BriarTestCase {
} }
} }
private void decreaseOutgoingMessageCounter(ClientHelper clientHelper,
GroupId g, int num) throws FormatException, DbException {
BdfDictionary gD = clientHelper.getGroupMetadataAsDictionary(g);
LOG.warning(gD.toString());
BdfDictionary queue = gD.getDictionary(QUEUE_STATE_KEY);
queue.put("nextOut", queue.getLong("nextOut") - num);
gD.put(QUEUE_STATE_KEY, queue);
clientHelper.mergeGroupMetadata(g, gD);
}
private void injectEagerSingletons( private void injectEagerSingletons(
IntroductionIntegrationTestComponent component) { IntroductionIntegrationTestComponent component) {

View File

@@ -3,6 +3,7 @@ package org.briarproject.api.introduction;
import static org.briarproject.api.introduction.IntroducerAction.ACK_1; import static org.briarproject.api.introduction.IntroducerAction.ACK_1;
import static org.briarproject.api.introduction.IntroducerAction.ACK_2; import static org.briarproject.api.introduction.IntroducerAction.ACK_2;
import static org.briarproject.api.introduction.IntroducerAction.LOCAL_REQUEST; import static org.briarproject.api.introduction.IntroducerAction.LOCAL_REQUEST;
import static org.briarproject.api.introduction.IntroducerAction.REMOTE_ABORT;
import static org.briarproject.api.introduction.IntroducerAction.REMOTE_ACCEPT_1; import static org.briarproject.api.introduction.IntroducerAction.REMOTE_ACCEPT_1;
import static org.briarproject.api.introduction.IntroducerAction.REMOTE_ACCEPT_2; import static org.briarproject.api.introduction.IntroducerAction.REMOTE_ACCEPT_2;
import static org.briarproject.api.introduction.IntroducerAction.REMOTE_DECLINE_1; import static org.briarproject.api.introduction.IntroducerAction.REMOTE_DECLINE_1;
@@ -65,7 +66,13 @@ public enum IntroducerProtocolState {
return ERROR; return ERROR;
} }
}, },
FINISHED(8); FINISHED(8) {
@Override
public IntroducerProtocolState next(IntroducerAction a) {
if (a == REMOTE_ABORT) return ERROR;
return FINISHED;
}
};
private final int value; private final int value;