mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 19:29:06 +01:00
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:
@@ -1,7 +1,10 @@
|
||||
package org.briarproject;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import net.jodah.concurrentunit.Waiter;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
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.sync.ClientId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.SyncSession;
|
||||
import org.briarproject.api.sync.SyncSessionFactory;
|
||||
@@ -59,6 +63,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -69,22 +74,15 @@ import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.TestPluginsModule.MAX_LATENCY;
|
||||
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_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.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.PUBLIC_KEY;
|
||||
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.TIME;
|
||||
import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
|
||||
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_RESPONSE;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
||||
@@ -95,11 +93,13 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IntroductionIntegrationTest extends BriarTestCase {
|
||||
|
||||
private LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2;
|
||||
private LifecycleManager lifecycleManager0, lifecycleManager1,
|
||||
lifecycleManager2;
|
||||
private SyncSessionFactory sync0, sync1, sync2;
|
||||
private ContactManager contactManager0, contactManager1, contactManager2;
|
||||
private ContactId contactId0, contactId1, contactId2;
|
||||
private IdentityManager identityManager0, identityManager1, identityManager2;
|
||||
private IdentityManager identityManager0, identityManager1,
|
||||
identityManager2;
|
||||
private LocalAuthor author0, author1, author2;
|
||||
|
||||
@Inject
|
||||
@@ -108,6 +108,8 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
||||
CryptoComponent crypto;
|
||||
@Inject
|
||||
AuthorFactory authorFactory;
|
||||
@Inject
|
||||
IntroductionGroupFactory introductionGroupFactory;
|
||||
|
||||
// objects accessed from background threads need to be volatile
|
||||
private volatile IntroductionManager introductionManager0;
|
||||
@@ -834,7 +836,7 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFakeResponse() throws Exception {
|
||||
public void testModifiedResponse() throws Exception {
|
||||
startLifecycles();
|
||||
try {
|
||||
addDefaultIdentities();
|
||||
@@ -856,122 +858,106 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
||||
introductionManager0
|
||||
.makeIntroduction(introducee1, introducee2, "Hi!", time);
|
||||
|
||||
// sync first request message
|
||||
// sync request messages
|
||||
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
|
||||
eventWaiter.await(TIMEOUT, 2);
|
||||
|
||||
// sync first response
|
||||
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response1Received);
|
||||
|
||||
// get SessionId
|
||||
List<IntroductionMessage> list = new ArrayList<>(
|
||||
introductionManager1.getIntroductionMessages(contactId0));
|
||||
assertEquals(2, list.size());
|
||||
assertTrue(list.get(0) instanceof IntroductionRequest);
|
||||
IntroductionRequest msg = (IntroductionRequest) list.get(0);
|
||||
SessionId sessionId = msg.getSessionId();
|
||||
// get response to be forwarded
|
||||
MessageId responseId = null;
|
||||
BdfDictionary response = null;
|
||||
Group g2 = introductionGroupFactory
|
||||
.createIntroductionGroup(introducee2);
|
||||
ClientHelper clientHelper0 = t0.getClientHelper();
|
||||
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
|
||||
IntroductionGroupFactory groupFactory =
|
||||
t0.getIntroductionGroupFactory();
|
||||
Group group = groupFactory.createIntroductionGroup(introducee1);
|
||||
// adapt outgoing message queue to removed message
|
||||
decreaseOutgoingMessageCounter(clientHelper0, g2.getId(), 1);
|
||||
|
||||
// get data for contact2
|
||||
long timestamp = clock.currentTimeMillis();
|
||||
KeyPair eKeyPair = crypto.generateAgreementKeyPair();
|
||||
byte[] ePublicKey = eKeyPair.getPublic().getEncoded();
|
||||
TransportProperties tp = new TransportProperties(
|
||||
Collections.singletonMap("key", "value"));
|
||||
BdfDictionary tpDict = BdfDictionary.of(new BdfEntry("fake", tp));
|
||||
// modify response by changing transport properties
|
||||
BdfDictionary tp = response.getDictionary(TRANSPORT);
|
||||
tp.put("fakeId", BdfDictionary.of(new BdfEntry("fake", "fake")));
|
||||
response.put(TRANSPORT, tp);
|
||||
|
||||
// create a fake response
|
||||
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();
|
||||
// replace original response with modified one
|
||||
MessageSender sender0 = t0.getMessageSender();
|
||||
DatabaseComponent db0 = t0.getDatabaseComponent();
|
||||
Transaction txn = db0.startTransaction(false);
|
||||
try {
|
||||
sender0.sendMessage(txn, d);
|
||||
db0.deleteMessage(txn, responseId);
|
||||
sender0.sendMessage(txn, response);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
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");
|
||||
|
||||
// fake session state for introducer, so she doesn't abort
|
||||
ClientHelper clientHelper0 = t0.getClientHelper();
|
||||
BdfDictionary state =
|
||||
clientHelper0.getMessageMetadataAsDictionary(sessionId);
|
||||
state.put(STATE, IntroducerProtocolState.AWAIT_ACKS.getValue());
|
||||
clientHelper0.mergeMessageMetadata(sessionId, state);
|
||||
|
||||
// sync back the ACK
|
||||
// sync second ACK and forward it
|
||||
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
|
||||
deliverMessage(sync0, contactId0, sync2, contactId2, "0 to 2");
|
||||
|
||||
// create a fake ACK
|
||||
// TODO do we need to actually calculate a MAC and signature here?
|
||||
byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
|
||||
byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
d = BdfDictionary.of(
|
||||
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);
|
||||
// introducee2 should have detected the fake now
|
||||
// and deleted introducee1 again
|
||||
Collection<Contact> contacts2;
|
||||
DatabaseComponent db2 = t2.getDatabaseComponent();
|
||||
txn = db2.startTransaction(true);
|
||||
try {
|
||||
sender0.sendMessage(txn, d);
|
||||
contacts2 = db2.getContacts(txn);
|
||||
txn.setComplete();
|
||||
} 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();
|
||||
txn = db1.startTransaction(true);
|
||||
try {
|
||||
assertEquals(2, db1.getContacts(txn).size());
|
||||
contacts1 = db1.getContacts(txn);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db1.endTransaction(txn);
|
||||
}
|
||||
|
||||
// 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());
|
||||
assertEquals(2, contacts1.size());
|
||||
} finally {
|
||||
stopLifecycles();
|
||||
}
|
||||
@@ -1067,7 +1053,7 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
||||
}
|
||||
|
||||
private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
|
||||
SyncSessionFactory toSync, ContactId toId, String debug)
|
||||
SyncSessionFactory toSync, ContactId toId, @Nullable String debug)
|
||||
throws IOException, TimeoutException {
|
||||
|
||||
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(
|
||||
IntroductionIntegrationTestComponent component) {
|
||||
|
||||
|
||||
@@ -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_2;
|
||||
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_2;
|
||||
import static org.briarproject.api.introduction.IntroducerAction.REMOTE_DECLINE_1;
|
||||
@@ -65,7 +66,13 @@ public enum IntroducerProtocolState {
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
FINISHED(8);
|
||||
FINISHED(8) {
|
||||
@Override
|
||||
public IntroducerProtocolState next(IntroducerAction a) {
|
||||
if (a == REMOTE_ABORT) return ERROR;
|
||||
return FINISHED;
|
||||
}
|
||||
};
|
||||
|
||||
private final int value;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user