diff --git a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java index 9d74b6d2a0fe91e94382b034721f8ff38f89e2ec..0de25f31f9f4542b8c9a5c2a9278ff804b38ef17 100644 --- a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java @@ -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) { diff --git a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java index 733082ef0bd446a8c9ab805e6a11091de9653833..8e3e770420efdd5bdf6c6133f25926b55cd971c4 100644 --- a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java +++ b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java @@ -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(); diff --git a/briar-api/src/org/briarproject/api/contact/ContactManager.java b/briar-api/src/org/briarproject/api/contact/ContactManager.java index 409d26d88f5de1cc6c1efb76e474e052093a88ba..5203da3004764c7d36909ca354a559d8e2905b7c 100644 --- a/briar-api/src/org/briarproject/api/contact/ContactManager.java +++ b/briar-api/src/org/briarproject/api/contact/ContactManager.java @@ -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, diff --git a/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java b/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java index 1806f76d72b91dc9fda9d0ea71c9b72fe8d68995..50a9c94d565d612f645fc5b5f8cbba561d937f18 100644 --- a/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java +++ b/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java @@ -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. diff --git a/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java b/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java index db50467f08b2fe29188f2c3b2352ab516bfd3652..324ba5bb613d7cb902af9aed0bff813c71669370 100644 --- a/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java +++ b/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java @@ -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; } diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java index c3376e36eedf76e38a7551ea5574ce993f117f5a..ac8975e5c2f7d83950dfcbf45a89353a5d615f1b 100644 --- a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java @@ -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; diff --git a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java index fb6ede72d4cddda86ad12e77c5402eb5fb7d0d16..44bc5979e6b8725109dede1b22783d10a1a097a1 100644 --- a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java +++ b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java @@ -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) diff --git a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java index 169bfb7818f9d8002cbeac905dc9451584a00d7b..22698a22ff70aac5087158a3f17684aab0c87686 100644 --- a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java +++ b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java @@ -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) { diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java index 50af8e754ad010b7d69a9b577afeeeefc8518b56..8f77539c79faee46489301b2fe982740ad089fa3 100644 --- a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java +++ b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java @@ -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 " + diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java index 325fed6475c2d6cc5d8aaf50831bfcd3cfc38154..0223c14b3e26b8616dc8d940d0483e4c90939d06 100644 --- a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java +++ b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java @@ -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); } } diff --git a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java index 7eacaccc839485c108841c03f2d068663e3cc7f5..3473ad61364779980c9868840ab3f9ad00f2c997 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java @@ -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) diff --git a/briar-core/src/org/briarproject/introduction/MessageSender.java b/briar-core/src/org/briarproject/introduction/MessageSender.java index 9327dc9e26c11b700b33fb0ee4ae3f1489be8bbd..bfa8138d8f4a6775599615e204223169930fc351 100644 --- a/briar-core/src/org/briarproject/introduction/MessageSender.java +++ b/briar-core/src/org/briarproject/introduction/MessageSender.java @@ -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 { diff --git a/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java b/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java index 8b7e1afe340f2fb98087213712809f5762172505..0a91bd6864a355e54feb799be4cf897bd45f83d6 100644 --- a/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java +++ b/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java @@ -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( diff --git a/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java b/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java index 5399abdbcaf9033663549b5080aa6637d79c607e..fa0fee5e47c9dc2bbaa1a1e172e8b681baeea194 100644 --- a/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java +++ b/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java @@ -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(); } diff --git a/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java b/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java index 8809fd0a1a5fdd6cbbcf95a9a84f2cbfbb83ca09..bbc7cf13d090508bf57da9d31c1e4abd2c2d2184 100644 --- a/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java +++ b/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java @@ -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();