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 5626b2ba04eb9bda6531321948a45f867c183168..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; @@ -66,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; @@ -90,6 +105,8 @@ public class IntroductionIntegrationTest extends BriarTestCase { @Inject Clock clock; @Inject + CryptoComponent crypto; + @Inject AuthorFactory authorFactory; // objects accessed from background threads need to be volatile @@ -154,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); @@ -259,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); @@ -352,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); @@ -440,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); @@ -519,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); @@ -669,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); @@ -766,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); @@ -852,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); @@ -948,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 { @@ -991,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 { 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/IntroductionConstants.java b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java index 9e45da0e74595c4752e5748cd25314a0dd5f6e37..ac8975e5c2f7d83950dfcbf45a89353a5d615f1b 100644 --- a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java @@ -29,6 +29,9 @@ public interface IntroductionConstants { String MAC = "mac"; String SIGNATURE = "signature"; + /* Validation Constants */ + int MAC_LENGTH = 32; + /* Introducer Local State Metadata */ String STATE = "state"; String ROLE = "role"; @@ -61,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 58cf2821c6d64996da207bc32c4a9e5081532502..8f77539c79faee46489301b2fe982740ad089fa3 100644 --- a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java +++ b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java @@ -46,7 +46,9 @@ import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TI 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; @@ -192,6 +194,7 @@ public class IntroduceeEngine // 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(); } @@ -241,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 { @@ -248,8 +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(MAC)); - m.put(SIGNATURE, localState.getRaw(SIGNATURE)); + 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 2c1db51ef900a175561459f65b1bb309f8b551dc..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; @@ -53,13 +57,18 @@ 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; @@ -92,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 @@ -100,6 +110,7 @@ class IntroduceeManager { CryptoComponent cryptoComponent, TransportPropertyManager transportPropertyManager, AuthorFactory authorFactory, ContactManager contactManager, + IdentityManager identityManager, IntroductionGroupFactory introductionGroupFactory) { this.messageSender = messageSender; @@ -110,6 +121,7 @@ class IntroduceeManager { this.transportPropertyManager = transportPropertyManager; this.authorFactory = authorFactory; this.contactManager = contactManager; + this.identityManager = identityManager; this.introductionGroupFactory = introductionGroupFactory; } @@ -189,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 @@ -311,24 +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); } - // TODO MAC and signature - localState.put(MAC, new byte[42]); - localState.put(SIGNATURE, new byte[42]); + // 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)); @@ -365,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); @@ -389,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 bf35d08f760e3a63ce57b7096c0ec1f3337e55cb..3473ad61364779980c9868840ab3f9ad00f2c997 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java @@ -20,6 +20,7 @@ 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; @@ -157,7 +158,7 @@ class IntroductionValidator extends BdfMessageValidator { checkSize(message, 4); byte[] mac = message.getRaw(2); - // TODO length check? + checkLength(mac, 1, MAC_LENGTH); byte[] sig = message.getRaw(3); checkLength(sig, 1, MAX_SIGNATURE_LENGTH); 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(