Merge branch '364-introduction-responses-should-be-signed' into 'master'

Sign Introduction Information to be able to discover MITM attack

The local introducee doesn't know whether each piece of information received from the introducer originates from the remote introducee or has been replaced by the introducer, i.e. whether the introducer is carrying out a man-in-the-middle attack.

The introduction protocol doesn't aim to detect or prevent man-in-the-middle attacks. We only aim to establish that if the remote identity public key is not replaced then the remote ephemeral public key, transport properties and timestamp are not replaced either. This MR adds a MAC and a signature to the introduction protocol's ACK message to fulfill that aim. See #364 for the detailed security argument.

Later, when the local introducee verifies that the remote identity public key belongs to a particular person (#513), she can also be sure that the remote ephemeral public key, transport properties and timestamp originated from that person.

Closes #364

See merge request !297
This commit is contained in:
akwizgran
2016-08-30 23:05:25 +00:00
15 changed files with 491 additions and 266 deletions

View File

@@ -2,10 +2,13 @@ package org.briarproject;
import net.jodah.concurrentunit.Waiter;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.Contact;
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.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
@@ -23,6 +26,7 @@ import org.briarproject.api.event.MessageStateChangedEvent;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.introduction.IntroducerProtocolState;
import org.briarproject.api.introduction.IntroductionManager;
import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest;
@@ -34,7 +38,6 @@ import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.SyncSession;
import org.briarproject.api.sync.SyncSessionFactory;
import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.sync.ValidationManager.State;
import org.briarproject.api.system.Clock;
import org.briarproject.contact.ContactModule;
@@ -67,12 +70,23 @@ import javax.inject.Inject;
import static org.briarproject.TestPluginsModule.MAX_LATENCY;
import static org.briarproject.TestPluginsModule.TRANSPORT_ID;
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;
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
import static org.junit.Assert.assertEquals;
@@ -81,16 +95,18 @@ import static org.junit.Assert.assertTrue;
public class IntroductionIntegrationTest extends BriarTestCase {
LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2;
SyncSessionFactory sync0, sync1, sync2;
ContactManager contactManager0, contactManager1, contactManager2;
ContactId contactId0, contactId1, contactId2;
IdentityManager identityManager0, identityManager1, identityManager2;
LocalAuthor author0, author1, author2;
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 LocalAuthor author0, author1, author2;
@Inject
Clock clock;
@Inject
CryptoComponent crypto;
@Inject
AuthorFactory authorFactory;
// objects accessed from background threads need to be volatile
@@ -155,32 +171,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
public void testIntroductionSession() throws Exception {
startLifecycles();
try {
// Add Identities
// Add Identities And Contacts
addDefaultIdentities();
addDefaultContacts();
// Add Transport Properties
addTransportProperties();
// Add introducees as contacts
contactId1 = contactManager0.addContact(author1,
author0.getId(), master, clock.currentTimeMillis(), true,
true, true
);
contactId2 = contactManager0.addContact(author2,
author0.getId(), master, clock.currentTimeMillis(), true,
true, true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true,
true, true
);
ContactId contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), true,
true, true
);
assertTrue(contactId0.equals(contactId02));
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
@@ -260,29 +257,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
public void testIntroductionSessionFirstDecline() throws Exception {
startLifecycles();
try {
// Add Identities
// Add Identities And Contacts
addDefaultIdentities();
addDefaultContacts();
// Add Transport Properties
addTransportProperties();
// Add introducees as contacts
contactId1 = contactManager0.addContact(author1, author0.getId(),
master, clock.currentTimeMillis(), true, true, true
);
contactId2 = contactManager0.addContact(author2, author0.getId(),
master, clock.currentTimeMillis(), true, true, true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0, author1.getId(),
master, clock.currentTimeMillis(), true, true, true
);
ContactId contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), true,
true, true
);
assertTrue(contactId0.equals(contactId02));
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
@@ -353,29 +334,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
public void testIntroductionSessionSecondDecline() throws Exception {
startLifecycles();
try {
// Add Identities
// Add Identities And Contacts
addDefaultIdentities();
addDefaultContacts();
// Add Transport Properties
addTransportProperties();
// Add introducees as contacts
contactId1 = contactManager0.addContact(author1, author0.getId(),
master, clock.currentTimeMillis(), true, true, true
);
contactId2 = contactManager0.addContact(author2, author0.getId(),
master, clock.currentTimeMillis(), true, true, true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0, author1.getId(),
master, clock.currentTimeMillis(), false, true, true
);
ContactId contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), false,
true, true
);
assertTrue(contactId0.equals(contactId02));
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
@@ -441,29 +406,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
public void testIntroductionSessionDelayedFirstDecline() throws Exception {
startLifecycles();
try {
// Add Identities
// Add Identities And Contacts
addDefaultIdentities();
addDefaultContacts();
// Add Transport Properties
addTransportProperties();
// Add introducees as contacts
contactId1 = contactManager0.addContact(author1, author0.getId(),
master, clock.currentTimeMillis(), true, true, true
);
contactId2 = contactManager0.addContact(author2, author0.getId(),
master, clock.currentTimeMillis(), true, true, true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0, author1.getId(),
master, clock.currentTimeMillis(), false, true, true
);
ContactId contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), false,
true, true
);
assertTrue(contactId0.equals(contactId02));
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
@@ -520,21 +469,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
public void testIntroductionToSameContact() throws Exception {
startLifecycles();
try {
// Add Identities
// Add Identities And Contacts
addDefaultIdentities();
addDefaultContacts();
// Add Transport Properties
addTransportProperties();
// Add introducee as contact
contactId1 = contactManager0.addContact(author1, author0.getId(),
master, clock.currentTimeMillis(), true, true, true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0, author1.getId(),
master, clock.currentTimeMillis(), true, true, true
);
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
@@ -670,32 +611,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
public void testSessionIdReuse() throws Exception {
startLifecycles();
try {
// Add Identities
// Add Identities And Contacts
addDefaultIdentities();
addDefaultContacts();
// Add Transport Properties
addTransportProperties();
// Add introducees as contacts
contactId1 = contactManager0.addContact(author1,
author0.getId(), master, clock.currentTimeMillis(), true,
true, true
);
contactId2 = contactManager0.addContact(author2,
author0.getId(), master, clock.currentTimeMillis(), true,
true, true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true,
true, true
);
ContactId contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), true,
true, true
);
assertTrue(contactId0.equals(contactId02));
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
@@ -767,32 +689,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
public void testIntroducerRemovedCleanup() throws Exception {
startLifecycles();
try {
// Add Identities
// Add Identities And Contacts
addDefaultIdentities();
addDefaultContacts();
// Add Transport Properties
addTransportProperties();
// Add introducees as contacts
contactId1 = contactManager0.addContact(author1,
author0.getId(), master, clock.currentTimeMillis(), true,
true, true
);
contactId2 = contactManager0.addContact(author2,
author0.getId(), master, clock.currentTimeMillis(), true,
true, true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true,
true, true
);
ContactId contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), true,
true, true
);
assertTrue(contactId0.equals(contactId02));
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
@@ -853,32 +756,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
public void testIntroduceesRemovedCleanup() throws Exception {
startLifecycles();
try {
// Add Identities
// Add Identities And Contacts
addDefaultIdentities();
addDefaultContacts();
// Add Transport Properties
addTransportProperties();
// Add introducees as contacts
contactId1 = contactManager0.addContact(author1,
author0.getId(), master, clock.currentTimeMillis(), true,
true, true
);
contactId2 = contactManager0.addContact(author2,
author0.getId(), master, clock.currentTimeMillis(), true,
true, true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true,
true, true
);
ContactId contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), true,
true, true
);
assertTrue(contactId0.equals(contactId02));
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
@@ -949,7 +833,149 @@ public class IntroductionIntegrationTest extends BriarTestCase {
}
}
// TODO add a test for faking responses when #256 is implemented
@Test
public void testFakeResponse() throws Exception {
startLifecycles();
try {
addDefaultIdentities();
addDefaultContacts();
addTransportProperties();
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
IntroduceeListener listener1 = new IntroduceeListener(1, true);
t1.getEventBus().addListener(listener1);
IntroduceeListener listener2 = new IntroduceeListener(2, true);
t2.getEventBus().addListener(listener2);
// make introduction
long time = clock.currentTimeMillis();
Contact introducee1 = contactManager0.getContact(contactId1);
Contact introducee2 = contactManager0.getContact(contactId2);
introductionManager0
.makeIntroduction(introducee1, introducee2, "Hi!", time);
// sync first request message
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// 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 contact group
IntroductionGroupFactory groupFactory =
t0.getIntroductionGroupFactory();
Group group = groupFactory.createIntroductionGroup(introducee1);
// 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));
// 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();
MessageSender sender0 = t0.getMessageSender();
Transaction txn = db0.startTransaction(false);
try {
sender0.sendMessage(txn, d);
txn.setComplete();
} finally {
db0.endTransaction(txn);
}
// send the fake response
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
deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
// 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);
try {
sender0.sendMessage(txn, d);
txn.setComplete();
} finally {
db0.endTransaction(txn);
}
// make sure the contact was already added (as inactive)
DatabaseComponent db1 = t1.getDatabaseComponent();
txn = db1.startTransaction(true);
try {
assertEquals(2, db1.getContacts(txn).size());
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());
} finally {
stopLifecycles();
}
}
@After
public void tearDown() throws InterruptedException {
@@ -992,20 +1018,48 @@ public class IntroductionIntegrationTest extends BriarTestCase {
}
private void addDefaultIdentities() throws DbException {
author0 = authorFactory.createLocalAuthor(INTRODUCER,
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
TestUtils.getRandomBytes(123));
KeyPair keyPair0 = crypto.generateSignatureKeyPair();
byte[] publicKey0 = keyPair0.getPublic().getEncoded();
byte[] privateKey0 = keyPair0.getPrivate().getEncoded();
author0 = authorFactory
.createLocalAuthor(INTRODUCER, publicKey0, privateKey0);
identityManager0.addLocalAuthor(author0);
author1 = authorFactory.createLocalAuthor(INTRODUCEE1,
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
TestUtils.getRandomBytes(123));
KeyPair keyPair1 = crypto.generateSignatureKeyPair();
byte[] publicKey1 = keyPair1.getPublic().getEncoded();
byte[] privateKey1 = keyPair1.getPrivate().getEncoded();
author1 = authorFactory
.createLocalAuthor(INTRODUCEE1, publicKey1, privateKey1);
identityManager1.addLocalAuthor(author1);
author2 = authorFactory.createLocalAuthor(INTRODUCEE2,
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
TestUtils.getRandomBytes(123));
KeyPair keyPair2 = crypto.generateSignatureKeyPair();
byte[] publicKey2 = keyPair2.getPublic().getEncoded();
byte[] privateKey2 = keyPair2.getPrivate().getEncoded();
author2 = authorFactory
.createLocalAuthor(INTRODUCEE2, publicKey2, privateKey2);
identityManager2.addLocalAuthor(author2);
}
private void addDefaultContacts() throws DbException {
// Add introducees as contacts
contactId1 = contactManager0.addContact(author1,
author0.getId(), master, clock.currentTimeMillis(), true,
true, true
);
contactId2 = contactManager0.addContact(author2,
author0.getId(), master, clock.currentTimeMillis(), true,
true, true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true,
true, true
);
ContactId contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), true,
true, true
);
assertTrue(contactId0.equals(contactId02));
}
private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
SyncSessionFactory toSync, ContactId toId)
throws IOException, TimeoutException {
@@ -1050,14 +1104,14 @@ public class IntroductionIntegrationTest extends BriarTestCase {
private class IntroduceeListener implements EventListener {
public volatile boolean requestReceived = false;
public volatile boolean succeeded = false;
public volatile boolean aborted = false;
private volatile boolean requestReceived = false;
private volatile boolean succeeded = false;
private volatile boolean aborted = false;
private final int introducee;
private final boolean accept;
IntroduceeListener(int introducee, boolean accept) {
private IntroduceeListener(int introducee, boolean accept) {
this.introducee = introducee;
this.accept = accept;
}
@@ -1126,9 +1180,9 @@ public class IntroductionIntegrationTest extends BriarTestCase {
private class IntroducerListener implements EventListener {
public volatile boolean response1Received = false;
public volatile boolean response2Received = false;
public volatile boolean aborted = false;
private volatile boolean response1Received = false;
private volatile boolean response2Received = false;
private volatile boolean aborted = false;
@Override
public void eventOccurred(Event e) {

View File

@@ -1,5 +1,6 @@
package org.briarproject;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.event.EventBus;
@@ -85,6 +86,8 @@ public interface IntroductionIntegrationTestComponent {
DatabaseComponent getDatabaseComponent();
ClientHelper getClientHelper();
MessageSender getMessageSender();
IntroductionGroupFactory getIntroductionGroupFactory();

View File

@@ -41,8 +41,12 @@ public interface ContactManager {
/** Removes a contact and all associated state. */
void removeContact(ContactId c) throws DbException;
/** Removes a contact and all associated state. */
void removeContact(Transaction txn, ContactId c) throws DbException;
/** Marks a contact as active or inactive. */
void setContactActive(ContactId c, boolean active) throws DbException;
void setContactActive(Transaction txn, ContactId c, boolean active)
throws DbException;
/** Return true if a contact with this name and public key already exists */
boolean contactExists(Transaction txn, AuthorId remoteAuthorId,

View File

@@ -43,6 +43,12 @@ public interface CryptoComponent {
*/
SecretKey deriveHeaderKey(SecretKey master, boolean alice);
/**
* Derives a message authentication code key from the given master secret.
* @param alice whether the key is for use by Alice or Bob.
*/
SecretKey deriveMacKey(SecretKey master, boolean alice);
/**
* Derives a nonce from the given master secret for one of the parties to
* sign.

View File

@@ -32,6 +32,7 @@ public enum IntroduceeAction {
public static IntroduceeAction getLocal(int type, boolean accept) {
if (type == TYPE_RESPONSE && accept) return LOCAL_ACCEPT;
if (type == TYPE_RESPONSE) return LOCAL_DECLINE;
if (type == TYPE_ACK) return ACK;
if (type == TYPE_ABORT) return LOCAL_ABORT;
return null;
}

View File

@@ -26,6 +26,11 @@ public interface IntroductionConstants {
String TRANSPORT = "transport";
String MESSAGE_ID = "messageId";
String MESSAGE_TIME = "timestamp";
String MAC = "mac";
String SIGNATURE = "signature";
/* Validation Constants */
int MAC_LENGTH = 32;
/* Introducer Local State Metadata */
String STATE = "state";
@@ -59,6 +64,11 @@ public interface IntroductionConstants {
String EXISTS = "contactExists";
String REMOTE_AUTHOR_IS_US = "remoteAuthorIsUs";
String ANSWERED = "answered";
String NONCE = "nonce";
String MAC_KEY = "macKey";
String OUR_TRANSPORT = "ourTransport";
String OUR_MAC = "ourMac";
String OUR_SIGNATURE = "ourSignature";
String TASK = "task";
int TASK_ADD_CONTACT = 0;

View File

@@ -114,15 +114,9 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
}
@Override
public void setContactActive(ContactId c, boolean active)
public void setContactActive(Transaction txn, ContactId c, boolean active)
throws DbException {
Transaction txn = db.startTransaction(false);
try {
db.setContactActive(txn, c, active);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
db.setContactActive(txn, c, active);
}
@Override
@@ -145,7 +139,8 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
return exists;
}
private void removeContact(Transaction txn, ContactId c)
@Override
public void removeContact(Transaction txn, ContactId c)
throws DbException {
Contact contact = db.getContact(txn, c);
for (RemoveContactHook hook : removeHooks)

View File

@@ -87,6 +87,9 @@ class CryptoComponentImpl implements CryptoComponent {
// KDF labels for header key derivation
private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY");
// KDF labels for MAC key derivation
private static final byte[] A_MAC = ascii("ALICE_MAC_KEY");
private static final byte[] B_MAC = ascii("BOB_MAC_KEY");
// KDF label for key rotation
private static final byte[] ROTATE = ascii("ROTATE");
@@ -233,6 +236,11 @@ class CryptoComponentImpl implements CryptoComponent {
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
}
@Override
public SecretKey deriveMacKey(SecretKey master, boolean alice) {
return new SecretKey(macKdf(master, alice ? A_MAC : B_MAC));
}
@Override
public byte[] deriveSignatureNonce(SecretKey master,
boolean alice) {

View File

@@ -22,6 +22,7 @@ import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.introduction.IntroduceeAction.ACK;
import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_ABORT;
import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_ACCEPT;
import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_DECLINE;
@@ -39,18 +40,22 @@ import static org.briarproject.api.introduction.IntroductionConstants.EXISTS;
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.INTRODUCER;
import static org.briarproject.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_ID;
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.api.introduction.IntroductionConstants.MSG;
import static org.briarproject.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_MAC;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_SIGNATURE;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_TIME;
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
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.TASK;
import static org.briarproject.api.introduction.IntroductionConstants.TASK_ABORT;
@@ -97,10 +102,10 @@ public class IntroduceeEngine
else return abortSession(currentState, localState);
}
List<BdfDictionary> messages = new ArrayList<BdfDictionary>(1);
if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) {
localState.put(STATE, nextState.getValue());
localState.put(ANSWERED, true);
List<BdfDictionary> messages = new ArrayList<BdfDictionary>(1);
// create the introduction response message
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_RESPONSE);
@@ -118,17 +123,18 @@ public class IntroduceeEngine
if (nextState == AWAIT_ACK) {
localState.put(TASK, TASK_ADD_CONTACT);
// also send ACK, because we already have the other response
BdfDictionary ack = getAckMessage(localState);
messages.add(ack);
}
List<Event> events = Collections.emptyList();
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
false,
localState, messages, events);
} else if (action == ACK) {
// just send ACK, don't update local state again
BdfDictionary ack = getAckMessage(localState);
messages.add(ack);
} else {
throw new IllegalArgumentException();
}
List<Event> events = Collections.emptyList();
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
false,
localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
@@ -181,16 +187,14 @@ public class IntroduceeEngine
addResponseData(localState, msg);
if (nextState == AWAIT_ACK) {
localState.put(TASK, TASK_ADD_CONTACT);
messages = Collections
.singletonList(getAckMessage(localState));
} else {
messages = Collections.emptyList();
}
messages = Collections.emptyList();
events = Collections.emptyList();
}
// we already sent our ACK and now received the other one
else if (currentState == AWAIT_ACK) {
localState.put(TASK, TASK_ACTIVATE_CONTACT);
addAckData(localState, msg);
messages = Collections.emptyList();
events = Collections.emptyList();
}
@@ -240,6 +244,13 @@ public class IntroduceeEngine
}
}
private void addAckData(BdfDictionary localState, BdfDictionary msg)
throws FormatException {
localState.put(MAC, msg.getRaw(MAC));
localState.put(SIGNATURE, msg.getRaw(SIGNATURE));
}
private BdfDictionary getAckMessage(BdfDictionary localState)
throws FormatException {
@@ -247,6 +258,8 @@ public class IntroduceeEngine
m.put(TYPE, TYPE_ACK);
m.put(GROUP_ID, localState.getRaw(GROUP_ID));
m.put(SESSION_ID, localState.getRaw(SESSION_ID));
m.put(MAC, localState.getRaw(OUR_MAC));
m.put(SIGNATURE, localState.getRaw(OUR_SIGNATURE));
if (LOG.isLoggable(INFO)) {
LOG.info("Sending ACK " + " to " +

View File

@@ -13,6 +13,7 @@ import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.crypto.PublicKey;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
@@ -23,6 +24,8 @@ import org.briarproject.api.event.IntroductionSucceededEvent;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.properties.TransportProperties;
import org.briarproject.api.properties.TransportPropertyManager;
import org.briarproject.api.sync.GroupId;
@@ -32,6 +35,7 @@ import org.briarproject.api.system.Clock;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
@@ -40,6 +44,7 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT;
import static org.briarproject.api.introduction.IntroductionConstants.ADDED_CONTACT_ID;
@@ -51,18 +56,25 @@ import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_K
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.api.introduction.IntroductionConstants.INTRODUCER;
import static org.briarproject.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID;
import static org.briarproject.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.api.introduction.IntroductionConstants.MAC_KEY;
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_ID;
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.api.introduction.IntroductionConstants.NONCE;
import static org.briarproject.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_MAC;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_PRIVATE_KEY;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_SIGNATURE;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_TIME;
import static org.briarproject.api.introduction.IntroductionConstants.OUR_TRANSPORT;
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
import static org.briarproject.api.introduction.IntroductionConstants.ROLE;
import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.api.introduction.IntroductionConstants.STORAGE_ID;
import static org.briarproject.api.introduction.IntroductionConstants.TASK;
@@ -73,6 +85,7 @@ 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_ABORT;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
class IntroduceeManager {
@@ -88,6 +101,7 @@ class IntroduceeManager {
private final TransportPropertyManager transportPropertyManager;
private final AuthorFactory authorFactory;
private final ContactManager contactManager;
private final IdentityManager identityManager;
private final IntroductionGroupFactory introductionGroupFactory;
@Inject
@@ -96,6 +110,7 @@ class IntroduceeManager {
CryptoComponent cryptoComponent,
TransportPropertyManager transportPropertyManager,
AuthorFactory authorFactory, ContactManager contactManager,
IdentityManager identityManager,
IntroductionGroupFactory introductionGroupFactory) {
this.messageSender = messageSender;
@@ -106,6 +121,7 @@ class IntroduceeManager {
this.transportPropertyManager = transportPropertyManager;
this.authorFactory = authorFactory;
this.contactManager = contactManager;
this.identityManager = identityManager;
this.introductionGroupFactory = introductionGroupFactory;
}
@@ -185,18 +201,19 @@ class IntroduceeManager {
byte[] privateKey = keyPair.getPrivate().getEncoded();
Map<TransportId, TransportProperties> transportProperties =
transportPropertyManager.getLocalProperties(txn);
BdfDictionary tp = encodeTransportProperties(transportProperties);
// update session state for later
state.put(ACCEPT, true);
state.put(OUR_TIME, now);
state.put(OUR_PUBLIC_KEY, publicKey);
state.put(OUR_PRIVATE_KEY, privateKey);
state.put(OUR_TRANSPORT, tp);
// define action
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, TYPE_RESPONSE);
localAction.put(TRANSPORT,
encodeTransportProperties(transportProperties));
localAction.put(TRANSPORT, tp);
localAction.put(MESSAGE_TIME, timestamp);
// start engine and process its state update
@@ -258,11 +275,12 @@ class IntroduceeManager {
private void performTasks(Transaction txn, BdfDictionary localState)
throws FormatException, DbException {
if (!localState.containsKey(TASK)) return;
if (!localState.containsKey(TASK) || localState.get(TASK) == NULL_VALUE)
return;
// remember task and remove it from localState
long task = localState.getLong(TASK);
localState.put(TASK, BdfDictionary.NULL_VALUE);
localState.put(TASK, NULL_VALUE);
if (task == TASK_ADD_CONTACT) {
if (localState.getBoolean(EXISTS)) {
@@ -306,20 +324,59 @@ class IntroduceeManager {
secretKey = cryptoComponent
.deriveMasterSecret(theirEphemeralKey, keyPair, alice);
} catch (GeneralSecurityException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
// we can not continue without the shared secret
throw new FormatException();
throw new DbException(e);
}
// Derive two nonces and a MAC key from the secret master key
byte[] ourNonce =
cryptoComponent.deriveSignatureNonce(secretKey, alice);
byte[] theirNonce =
cryptoComponent.deriveSignatureNonce(secretKey, !alice);
SecretKey macKey = cryptoComponent.deriveMacKey(secretKey, alice);
SecretKey theirMacKey =
cryptoComponent.deriveMacKey(secretKey, !alice);
// Save the other nonce and MAC key for the verification
localState.put(NONCE, theirNonce);
localState.put(MAC_KEY, theirMacKey.getBytes());
// Sign our nonce with our long-term identity public key
AuthorId localAuthorId =
new AuthorId(localState.getRaw(LOCAL_AUTHOR_ID));
LocalAuthor author =
identityManager.getLocalAuthor(txn, localAuthorId);
Signature signature = cryptoComponent.getSignature();
KeyParser sigParser = cryptoComponent.getSignatureKeyParser();
try {
PrivateKey privKey =
sigParser.parsePrivateKey(author.getPrivateKey());
signature.initSign(privKey);
} catch (GeneralSecurityException e) {
// we can not continue without the signature
throw new DbException(e);
}
signature.update(ourNonce);
byte[] sig = signature.sign();
// The agreed timestamp is the minimum of the peers' timestamps
long ourTime = localState.getLong(OUR_TIME);
long theirTime = localState.getLong(TIME);
long timestamp = Math.min(ourTime, theirTime);
// Add the contact to the database
AuthorId localAuthorId =
new AuthorId(localState.getRaw(LOCAL_AUTHOR_ID));
// Calculate a MAC over identity public key, ephemeral public key,
// transport properties and timestamp.
BdfDictionary tp = localState.getDictionary(OUR_TRANSPORT);
BdfList toSignList = BdfList.of(author.getPublicKey(),
publicKeyBytes, tp, ourTime);
byte[] toSign = clientHelper.toByteArray(toSignList);
byte[] mac = cryptoComponent.mac(macKey, toSign);
// Add MAC and signature to localState, so it can be included in ACK
localState.put(OUR_MAC, mac);
localState.put(OUR_SIGNATURE, sig);
// Add the contact to the database as inactive
Author remoteAuthor = authorFactory
.createAuthor(localState.getString(NAME),
localState.getRaw(PUBLIC_KEY));
@@ -339,7 +396,16 @@ class IntroduceeManager {
// delete the ephemeral private key by overwriting with NULL value
// this ensures future ephemeral keys can not be recovered when
// this device should gets compromised
localState.put(OUR_PRIVATE_KEY, BdfDictionary.NULL_VALUE);
localState.put(OUR_PRIVATE_KEY, NULL_VALUE);
// define next action: Send ACK
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, TYPE_ACK);
// start engine and process its state update
IntroduceeEngine engine = new IntroduceeEngine();
processStateUpdate(txn, null,
engine.onLocalAction(localState, localAction));
}
// we sent and received an ACK, so activate contact
@@ -347,13 +413,57 @@ class IntroduceeManager {
if (!localState.getBoolean(EXISTS) &&
localState.containsKey(ADDED_CONTACT_ID)) {
LOG.info("Verifying Signature...");
byte[] nonce = localState.getRaw(NONCE);
byte[] sig = localState.getRaw(SIGNATURE);
byte[] keyBytes = localState.getRaw(PUBLIC_KEY);
try {
// Parse the public key
KeyParser keyParser = cryptoComponent.getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(keyBytes);
// Verify the signature
Signature signature = cryptoComponent.getSignature();
signature.initVerify(key);
signature.update(nonce);
if (!signature.verify(sig)) {
LOG.warning("Invalid nonce signature in ACK");
throw new GeneralSecurityException();
}
} catch (GeneralSecurityException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
// we can not continue without verifying the signature
throw new DbException(e);
}
LOG.info("Verifying MAC...");
// get MAC and MAC key from session state
byte[] mac = localState.getRaw(MAC);
byte[] macKeyBytes = localState.getRaw(MAC_KEY);
SecretKey macKey = new SecretKey(macKeyBytes);
// get MAC data and calculate a new MAC with stored key
byte[] pubKey = localState.getRaw(PUBLIC_KEY);
byte[] ePubKey = localState.getRaw(E_PUBLIC_KEY);
BdfDictionary tp = localState.getDictionary(TRANSPORT);
long timestamp = localState.getLong(TIME);
BdfList toSignList = BdfList.of(pubKey, ePubKey, tp, timestamp);
byte[] toSign = clientHelper.toByteArray(toSignList);
byte[] calculatedMac = cryptoComponent.mac(macKey, toSign);
if (!Arrays.equals(mac, calculatedMac)) {
LOG.warning("Received ACK with invalid MAC");
throw new DbException();
}
LOG.info("Activating Contact...");
ContactId contactId = new ContactId(
localState.getLong(ADDED_CONTACT_ID).intValue());
// activate and show contact in contact list
db.setContactActive(txn, contactId, true);
contactManager.setContactActive(txn, contactId, true);
// broadcast event informing of successful introduction
Contact contact = db.getContact(txn, contactId);
@@ -371,7 +481,7 @@ class IntroduceeManager {
LOG.info("Deleting added contact due to abort...");
ContactId contactId = new ContactId(
localState.getLong(ADDED_CONTACT_ID).intValue());
contactManager.removeContact(contactId);
contactManager.removeContact(txn, contactId);
}
}

View File

@@ -1,9 +1,9 @@
package org.briarproject.introduction;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.clients.BdfMessageContext;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
@@ -15,15 +15,19 @@ import org.briarproject.clients.BdfMessageValidator;
import static org.briarproject.api.TransportId.MAX_TRANSPORT_ID_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_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.MESSAGE_ID;
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.api.introduction.IntroductionConstants.MSG;
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.TIME;
import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
@@ -150,13 +154,20 @@ class IntroductionValidator extends BdfMessageValidator {
return d;
}
private BdfDictionary validateAck(BdfList message)
throws FormatException {
private BdfDictionary validateAck(BdfList message) throws FormatException {
checkSize(message, 4);
checkSize(message, 2);
byte[] mac = message.getRaw(2);
checkLength(mac, 1, MAC_LENGTH);
byte[] sig = message.getRaw(3);
checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
// Return the metadata
return new BdfDictionary();
BdfDictionary d = new BdfDictionary();
d.put(MAC, mac);
d.put(SIGNATURE, sig);
return d;
}
private BdfDictionary validateAbort(BdfList message)

View File

@@ -19,11 +19,13 @@ import javax.inject.Inject;
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.MESSAGE_TIME;
import static org.briarproject.api.introduction.IntroductionConstants.MSG;
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.TIME;
import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
@@ -108,12 +110,12 @@ public class MessageSender {
list.add(d.getRaw(E_PUBLIC_KEY));
list.add(d.getDictionary(TRANSPORT));
}
// TODO Sign the response, see #256
return list;
}
private BdfList encodeAck(BdfDictionary d) throws FormatException {
return BdfList.of(TYPE_ACK, d.getRaw(SESSION_ID));
return BdfList.of(TYPE_ACK, d.getRaw(SESSION_ID),
d.getRaw(MAC), d.getRaw(SIGNATURE));
}
private BdfList encodeAbort(BdfDictionary d) throws FormatException {

View File

@@ -5,6 +5,7 @@ import org.briarproject.TestUtils;
import org.briarproject.api.Bytes;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
@@ -18,8 +19,8 @@ import org.briarproject.api.db.Transaction;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.introduction.IntroduceeProtocolState;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.properties.TransportPropertyManager;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
@@ -63,34 +64,33 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class IntroduceeManagerTest extends BriarTestCase {
final Mockery context;
final IntroduceeManager introduceeManager;
final DatabaseComponent db;
final CryptoComponent cryptoComponent;
final ClientHelper clientHelper;
final IntroductionGroupFactory introductionGroupFactory;
final MessageSender messageSender;
final TransportPropertyManager transportPropertyManager;
final AuthorFactory authorFactory;
final ContactManager contactManager;
final Clock clock;
final Contact introducer;
final Contact introducee1;
final Contact introducee2;
final Group localGroup1;
final Group introductionGroup1;
final Group introductionGroup2;
final Transaction txn;
final long time = 42L;
final Message localStateMessage;
final ClientId clientId;
final SessionId sessionId;
final Message message1;
private final Mockery context;
private final IntroduceeManager introduceeManager;
private final DatabaseComponent db;
private final CryptoComponent cryptoComponent;
private final ClientHelper clientHelper;
private final IntroductionGroupFactory introductionGroupFactory;
private final MessageSender messageSender;
private final TransportPropertyManager transportPropertyManager;
private final AuthorFactory authorFactory;
private final ContactManager contactManager;
private final IdentityManager identityManager;
private final Clock clock;
private final Contact introducer;
private final Contact introducee1;
private final Contact introducee2;
private final Group localGroup1;
private final Group introductionGroup1;
private final Transaction txn;
private final long time = 42L;
private final Message localStateMessage;
private final ClientId clientId;
private final SessionId sessionId;
private final Message message1;
public IntroduceeManagerTest() {
context = new Mockery();
@@ -105,10 +105,12 @@ public class IntroduceeManagerTest extends BriarTestCase {
transportPropertyManager = context.mock(TransportPropertyManager.class);
authorFactory = context.mock(AuthorFactory.class);
contactManager = context.mock(ContactManager.class);
identityManager = context.mock(IdentityManager.class);
introduceeManager = new IntroduceeManager(messageSender, db,
clientHelper, clock, cryptoComponent, transportPropertyManager,
authorFactory, contactManager, introductionGroupFactory);
authorFactory, contactManager, identityManager,
introductionGroupFactory);
AuthorId authorId0 = new AuthorId(TestUtils.getRandomId());
Author author0 = new Author(authorId0, "Introducer",
@@ -138,8 +140,6 @@ public class IntroduceeManagerTest extends BriarTestCase {
clientId, new byte[0]);
introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
clientId, new byte[0]);
introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()),
clientId, new byte[0]);
sessionId = new SessionId(TestUtils.getRandomId());
localStateMessage = new Message(

View File

@@ -5,11 +5,11 @@ import org.briarproject.TestUtils;
import org.briarproject.api.FormatException;
import org.briarproject.api.TransportId;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
@@ -24,13 +24,16 @@ import java.io.IOException;
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.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.MSG;
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.TIME;
import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
@@ -40,6 +43,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUE
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -281,19 +285,18 @@ public class IntroductionValidatorTest extends BriarTestCase {
@Test
public void testValidateProperIntroductionAck() throws IOException {
final byte[] sessionId = TestUtils.getRandomId();
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_ACK);
msg.put(SESSION_ID, sessionId);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
byte[] sessionId = TestUtils.getRandomId();
byte[] mac = TestUtils.getRandomBytes(42);
byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
BdfList body = BdfList.of(TYPE_ACK, sessionId, mac, sig);
BdfDictionary result =
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
assertArrayEquals(sessionId, result.getRaw(SESSION_ID));
assertArrayEquals(mac, result.getRaw(MAC));
assertArrayEquals(sig, result.getRaw(SIGNATURE));
context.assertIsSatisfied();
}

View File

@@ -5,7 +5,7 @@ import org.briarproject.TestUtils;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.MessageQueueManager;
import org.briarproject.api.clients.PrivateGroupFactory;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList;
@@ -14,7 +14,6 @@ import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
@@ -24,26 +23,27 @@ import org.jmock.Mockery;
import org.junit.Test;
import static junit.framework.Assert.assertFalse;
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK;
public class MessageSenderTest extends BriarTestCase {
final Mockery context;
final MessageSender messageSender;
final DatabaseComponent db;
final PrivateGroupFactory privateGroupFactory;
final ClientHelper clientHelper;
final MetadataEncoder metadataEncoder;
final MessageQueueManager messageQueueManager;
final Clock clock;
private final Mockery context;
private final MessageSender messageSender;
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final MetadataEncoder metadataEncoder;
private final MessageQueueManager messageQueueManager;
private final Clock clock;
public MessageSenderTest() {
context = new Mockery();
db = context.mock(DatabaseComponent.class);
privateGroupFactory = context.mock(PrivateGroupFactory.class);
clientHelper = context.mock(ClientHelper.class);
metadataEncoder =
context.mock(MetadataEncoder.class);
@@ -59,17 +59,22 @@ public class MessageSenderTest extends BriarTestCase {
@Test
public void testSendMessage() throws DbException, FormatException {
final Transaction txn = new Transaction(null, false);
final Group privateGroup = new Group(new GroupId(TestUtils.getRandomId()),
new ClientId(TestUtils.getRandomId()), new byte[0]);
final Group privateGroup =
new Group(new GroupId(TestUtils.getRandomId()),
new ClientId(TestUtils.getRandomId()), new byte[0]);
final SessionId sessionId = new SessionId(TestUtils.getRandomId());
byte[] mac = TestUtils.getRandomBytes(42);
byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
final long time = 42L;
final BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ACK),
new BdfEntry(GROUP_ID, privateGroup.getId()),
new BdfEntry(SESSION_ID, sessionId)
new BdfEntry(SESSION_ID, sessionId),
new BdfEntry(MAC, mac),
new BdfEntry(SIGNATURE, sig)
);
final BdfList bodyList =
BdfList.of(TYPE_ACK, msg.getRaw(SESSION_ID));
BdfList.of(TYPE_ACK, sessionId.getBytes(), mac, sig);
final byte[] body = TestUtils.getRandomBytes(8);
final Metadata metadata = new Metadata();