mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-19 14:19:53 +01:00
Calculate and verify signature and MAC for Introduction ACKs
Before the introducee sends her ACK, she derives a master key from the ephemeral shared secret as before. Two nonces and a MAC key are then derived from the master key. The local introducee signs one of the nonces and calculates a MAC over her own identity public key, ephemeral public key, transport properties and timestamp. The local introducee includes the signature and MAC in her ACK. On receiving the remote introducee's ACK, the local introducee verifies the signature and MAC. Should the verification fail, an ABORT is sent to the introducer and the remote introducee that was added as inactive is deleted again.
This commit is contained in:
@@ -2,10 +2,13 @@ package org.briarproject;
|
|||||||
|
|
||||||
import net.jodah.concurrentunit.Waiter;
|
import net.jodah.concurrentunit.Waiter;
|
||||||
|
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.clients.SessionId;
|
import org.briarproject.api.clients.SessionId;
|
||||||
import org.briarproject.api.contact.Contact;
|
import org.briarproject.api.contact.Contact;
|
||||||
import org.briarproject.api.contact.ContactId;
|
import org.briarproject.api.contact.ContactId;
|
||||||
import org.briarproject.api.contact.ContactManager;
|
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.crypto.SecretKey;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfEntry;
|
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.AuthorFactory;
|
||||||
import org.briarproject.api.identity.IdentityManager;
|
import org.briarproject.api.identity.IdentityManager;
|
||||||
import org.briarproject.api.identity.LocalAuthor;
|
import org.briarproject.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.api.introduction.IntroducerProtocolState;
|
||||||
import org.briarproject.api.introduction.IntroductionManager;
|
import org.briarproject.api.introduction.IntroductionManager;
|
||||||
import org.briarproject.api.introduction.IntroductionMessage;
|
import org.briarproject.api.introduction.IntroductionMessage;
|
||||||
import org.briarproject.api.introduction.IntroductionRequest;
|
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.MAX_LATENCY;
|
||||||
import static org.briarproject.TestPluginsModule.TRANSPORT_ID;
|
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_PUBLIC_KEY_LENGTH;
|
||||||
|
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||||
|
import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT;
|
||||||
|
import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
|
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
|
||||||
|
import static org.briarproject.api.introduction.IntroductionConstants.MAC;
|
||||||
|
import static org.briarproject.api.introduction.IntroductionConstants.MAC_LENGTH;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.NAME;
|
import static org.briarproject.api.introduction.IntroductionConstants.NAME;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
|
import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
|
||||||
|
import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
|
||||||
|
import static org.briarproject.api.introduction.IntroductionConstants.STATE;
|
||||||
|
import static org.briarproject.api.introduction.IntroductionConstants.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;
|
||||||
|
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||||
|
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||||
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
||||||
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
|
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -90,6 +105,8 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
|||||||
@Inject
|
@Inject
|
||||||
Clock clock;
|
Clock clock;
|
||||||
@Inject
|
@Inject
|
||||||
|
CryptoComponent crypto;
|
||||||
|
@Inject
|
||||||
AuthorFactory authorFactory;
|
AuthorFactory authorFactory;
|
||||||
|
|
||||||
// objects accessed from background threads need to be volatile
|
// objects accessed from background threads need to be volatile
|
||||||
@@ -154,32 +171,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
|||||||
public void testIntroductionSession() throws Exception {
|
public void testIntroductionSession() throws Exception {
|
||||||
startLifecycles();
|
startLifecycles();
|
||||||
try {
|
try {
|
||||||
// Add Identities
|
// Add Identities And Contacts
|
||||||
addDefaultIdentities();
|
addDefaultIdentities();
|
||||||
|
addDefaultContacts();
|
||||||
|
|
||||||
// Add Transport Properties
|
// Add Transport Properties
|
||||||
addTransportProperties();
|
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
|
// listen to events
|
||||||
IntroducerListener listener0 = new IntroducerListener();
|
IntroducerListener listener0 = new IntroducerListener();
|
||||||
t0.getEventBus().addListener(listener0);
|
t0.getEventBus().addListener(listener0);
|
||||||
@@ -259,29 +257,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
|||||||
public void testIntroductionSessionFirstDecline() throws Exception {
|
public void testIntroductionSessionFirstDecline() throws Exception {
|
||||||
startLifecycles();
|
startLifecycles();
|
||||||
try {
|
try {
|
||||||
// Add Identities
|
// Add Identities And Contacts
|
||||||
addDefaultIdentities();
|
addDefaultIdentities();
|
||||||
|
addDefaultContacts();
|
||||||
|
|
||||||
// Add Transport Properties
|
// Add Transport Properties
|
||||||
addTransportProperties();
|
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
|
// listen to events
|
||||||
IntroducerListener listener0 = new IntroducerListener();
|
IntroducerListener listener0 = new IntroducerListener();
|
||||||
t0.getEventBus().addListener(listener0);
|
t0.getEventBus().addListener(listener0);
|
||||||
@@ -352,29 +334,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
|||||||
public void testIntroductionSessionSecondDecline() throws Exception {
|
public void testIntroductionSessionSecondDecline() throws Exception {
|
||||||
startLifecycles();
|
startLifecycles();
|
||||||
try {
|
try {
|
||||||
// Add Identities
|
// Add Identities And Contacts
|
||||||
addDefaultIdentities();
|
addDefaultIdentities();
|
||||||
|
addDefaultContacts();
|
||||||
|
|
||||||
// Add Transport Properties
|
// Add Transport Properties
|
||||||
addTransportProperties();
|
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
|
// listen to events
|
||||||
IntroducerListener listener0 = new IntroducerListener();
|
IntroducerListener listener0 = new IntroducerListener();
|
||||||
t0.getEventBus().addListener(listener0);
|
t0.getEventBus().addListener(listener0);
|
||||||
@@ -440,29 +406,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
|||||||
public void testIntroductionSessionDelayedFirstDecline() throws Exception {
|
public void testIntroductionSessionDelayedFirstDecline() throws Exception {
|
||||||
startLifecycles();
|
startLifecycles();
|
||||||
try {
|
try {
|
||||||
// Add Identities
|
// Add Identities And Contacts
|
||||||
addDefaultIdentities();
|
addDefaultIdentities();
|
||||||
|
addDefaultContacts();
|
||||||
|
|
||||||
// Add Transport Properties
|
// Add Transport Properties
|
||||||
addTransportProperties();
|
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
|
// listen to events
|
||||||
IntroducerListener listener0 = new IntroducerListener();
|
IntroducerListener listener0 = new IntroducerListener();
|
||||||
t0.getEventBus().addListener(listener0);
|
t0.getEventBus().addListener(listener0);
|
||||||
@@ -519,21 +469,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
|||||||
public void testIntroductionToSameContact() throws Exception {
|
public void testIntroductionToSameContact() throws Exception {
|
||||||
startLifecycles();
|
startLifecycles();
|
||||||
try {
|
try {
|
||||||
// Add Identities
|
// Add Identities And Contacts
|
||||||
addDefaultIdentities();
|
addDefaultIdentities();
|
||||||
|
addDefaultContacts();
|
||||||
|
|
||||||
// Add Transport Properties
|
// Add Transport Properties
|
||||||
addTransportProperties();
|
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
|
// listen to events
|
||||||
IntroducerListener listener0 = new IntroducerListener();
|
IntroducerListener listener0 = new IntroducerListener();
|
||||||
t0.getEventBus().addListener(listener0);
|
t0.getEventBus().addListener(listener0);
|
||||||
@@ -669,32 +611,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
|||||||
public void testSessionIdReuse() throws Exception {
|
public void testSessionIdReuse() throws Exception {
|
||||||
startLifecycles();
|
startLifecycles();
|
||||||
try {
|
try {
|
||||||
// Add Identities
|
// Add Identities And Contacts
|
||||||
addDefaultIdentities();
|
addDefaultIdentities();
|
||||||
|
addDefaultContacts();
|
||||||
|
|
||||||
// Add Transport Properties
|
// Add Transport Properties
|
||||||
addTransportProperties();
|
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
|
// listen to events
|
||||||
IntroducerListener listener0 = new IntroducerListener();
|
IntroducerListener listener0 = new IntroducerListener();
|
||||||
t0.getEventBus().addListener(listener0);
|
t0.getEventBus().addListener(listener0);
|
||||||
@@ -766,32 +689,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
|||||||
public void testIntroducerRemovedCleanup() throws Exception {
|
public void testIntroducerRemovedCleanup() throws Exception {
|
||||||
startLifecycles();
|
startLifecycles();
|
||||||
try {
|
try {
|
||||||
// Add Identities
|
// Add Identities And Contacts
|
||||||
addDefaultIdentities();
|
addDefaultIdentities();
|
||||||
|
addDefaultContacts();
|
||||||
|
|
||||||
// Add Transport Properties
|
// Add Transport Properties
|
||||||
addTransportProperties();
|
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
|
// listen to events
|
||||||
IntroducerListener listener0 = new IntroducerListener();
|
IntroducerListener listener0 = new IntroducerListener();
|
||||||
t0.getEventBus().addListener(listener0);
|
t0.getEventBus().addListener(listener0);
|
||||||
@@ -852,32 +756,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
|||||||
public void testIntroduceesRemovedCleanup() throws Exception {
|
public void testIntroduceesRemovedCleanup() throws Exception {
|
||||||
startLifecycles();
|
startLifecycles();
|
||||||
try {
|
try {
|
||||||
// Add Identities
|
// Add Identities And Contacts
|
||||||
addDefaultIdentities();
|
addDefaultIdentities();
|
||||||
|
addDefaultContacts();
|
||||||
|
|
||||||
// Add Transport Properties
|
// Add Transport Properties
|
||||||
addTransportProperties();
|
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
|
// listen to events
|
||||||
IntroducerListener listener0 = new IntroducerListener();
|
IntroducerListener listener0 = new IntroducerListener();
|
||||||
t0.getEventBus().addListener(listener0);
|
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
|
@After
|
||||||
public void tearDown() throws InterruptedException {
|
public void tearDown() throws InterruptedException {
|
||||||
@@ -991,20 +1018,48 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addDefaultIdentities() throws DbException {
|
private void addDefaultIdentities() throws DbException {
|
||||||
author0 = authorFactory.createLocalAuthor(INTRODUCER,
|
KeyPair keyPair0 = crypto.generateSignatureKeyPair();
|
||||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
byte[] publicKey0 = keyPair0.getPublic().getEncoded();
|
||||||
TestUtils.getRandomBytes(123));
|
byte[] privateKey0 = keyPair0.getPrivate().getEncoded();
|
||||||
|
author0 = authorFactory
|
||||||
|
.createLocalAuthor(INTRODUCER, publicKey0, privateKey0);
|
||||||
identityManager0.addLocalAuthor(author0);
|
identityManager0.addLocalAuthor(author0);
|
||||||
author1 = authorFactory.createLocalAuthor(INTRODUCEE1,
|
KeyPair keyPair1 = crypto.generateSignatureKeyPair();
|
||||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
byte[] publicKey1 = keyPair1.getPublic().getEncoded();
|
||||||
TestUtils.getRandomBytes(123));
|
byte[] privateKey1 = keyPair1.getPrivate().getEncoded();
|
||||||
|
author1 = authorFactory
|
||||||
|
.createLocalAuthor(INTRODUCEE1, publicKey1, privateKey1);
|
||||||
identityManager1.addLocalAuthor(author1);
|
identityManager1.addLocalAuthor(author1);
|
||||||
author2 = authorFactory.createLocalAuthor(INTRODUCEE2,
|
KeyPair keyPair2 = crypto.generateSignatureKeyPair();
|
||||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
byte[] publicKey2 = keyPair2.getPublic().getEncoded();
|
||||||
TestUtils.getRandomBytes(123));
|
byte[] privateKey2 = keyPair2.getPrivate().getEncoded();
|
||||||
|
author2 = authorFactory
|
||||||
|
.createLocalAuthor(INTRODUCEE2, publicKey2, privateKey2);
|
||||||
identityManager2.addLocalAuthor(author2);
|
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,
|
private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
|
||||||
SyncSessionFactory toSync, ContactId toId)
|
SyncSessionFactory toSync, ContactId toId)
|
||||||
throws IOException, TimeoutException {
|
throws IOException, TimeoutException {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject;
|
package org.briarproject;
|
||||||
|
|
||||||
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
import org.briarproject.api.contact.ContactManager;
|
import org.briarproject.api.contact.ContactManager;
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
import org.briarproject.api.db.DatabaseComponent;
|
||||||
import org.briarproject.api.event.EventBus;
|
import org.briarproject.api.event.EventBus;
|
||||||
@@ -85,6 +86,8 @@ public interface IntroductionIntegrationTestComponent {
|
|||||||
|
|
||||||
DatabaseComponent getDatabaseComponent();
|
DatabaseComponent getDatabaseComponent();
|
||||||
|
|
||||||
|
ClientHelper getClientHelper();
|
||||||
|
|
||||||
MessageSender getMessageSender();
|
MessageSender getMessageSender();
|
||||||
|
|
||||||
IntroductionGroupFactory getIntroductionGroupFactory();
|
IntroductionGroupFactory getIntroductionGroupFactory();
|
||||||
|
|||||||
@@ -41,8 +41,12 @@ public interface ContactManager {
|
|||||||
/** Removes a contact and all associated state. */
|
/** Removes a contact and all associated state. */
|
||||||
void removeContact(ContactId c) throws DbException;
|
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. */
|
/** 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 */
|
/** Return true if a contact with this name and public key already exists */
|
||||||
boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
|
boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ public interface CryptoComponent {
|
|||||||
*/
|
*/
|
||||||
SecretKey deriveHeaderKey(SecretKey master, boolean alice);
|
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
|
* Derives a nonce from the given master secret for one of the parties to
|
||||||
* sign.
|
* sign.
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ public interface IntroductionConstants {
|
|||||||
String MAC = "mac";
|
String MAC = "mac";
|
||||||
String SIGNATURE = "signature";
|
String SIGNATURE = "signature";
|
||||||
|
|
||||||
|
/* Validation Constants */
|
||||||
|
int MAC_LENGTH = 32;
|
||||||
|
|
||||||
/* Introducer Local State Metadata */
|
/* Introducer Local State Metadata */
|
||||||
String STATE = "state";
|
String STATE = "state";
|
||||||
String ROLE = "role";
|
String ROLE = "role";
|
||||||
@@ -61,6 +64,11 @@ public interface IntroductionConstants {
|
|||||||
String EXISTS = "contactExists";
|
String EXISTS = "contactExists";
|
||||||
String REMOTE_AUTHOR_IS_US = "remoteAuthorIsUs";
|
String REMOTE_AUTHOR_IS_US = "remoteAuthorIsUs";
|
||||||
String ANSWERED = "answered";
|
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";
|
String TASK = "task";
|
||||||
int TASK_ADD_CONTACT = 0;
|
int TASK_ADD_CONTACT = 0;
|
||||||
|
|||||||
@@ -114,15 +114,9 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setContactActive(ContactId c, boolean active)
|
public void setContactActive(Transaction txn, ContactId c, boolean active)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Transaction txn = db.startTransaction(false);
|
db.setContactActive(txn, c, active);
|
||||||
try {
|
|
||||||
db.setContactActive(txn, c, active);
|
|
||||||
txn.setComplete();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -145,7 +139,8 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
|
|||||||
return exists;
|
return exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeContact(Transaction txn, ContactId c)
|
@Override
|
||||||
|
public void removeContact(Transaction txn, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Contact contact = db.getContact(txn, c);
|
Contact contact = db.getContact(txn, c);
|
||||||
for (RemoveContactHook hook : removeHooks)
|
for (RemoveContactHook hook : removeHooks)
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
// KDF labels for header key derivation
|
// KDF labels for header key derivation
|
||||||
private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
|
private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
|
||||||
private static final byte[] B_HEADER = ascii("BOB_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
|
// KDF label for key rotation
|
||||||
private static final byte[] ROTATE = ascii("ROTATE");
|
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));
|
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
|
@Override
|
||||||
public byte[] deriveSignatureNonce(SecretKey master,
|
public byte[] deriveSignatureNonce(SecretKey master,
|
||||||
boolean alice) {
|
boolean alice) {
|
||||||
|
|||||||
@@ -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.MSG;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.NAME;
|
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.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_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_TIME;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
|
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
|
// we already sent our ACK and now received the other one
|
||||||
else if (currentState == AWAIT_ACK) {
|
else if (currentState == AWAIT_ACK) {
|
||||||
localState.put(TASK, TASK_ACTIVATE_CONTACT);
|
localState.put(TASK, TASK_ACTIVATE_CONTACT);
|
||||||
|
addAckData(localState, msg);
|
||||||
messages = Collections.emptyList();
|
messages = Collections.emptyList();
|
||||||
events = 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)
|
private BdfDictionary getAckMessage(BdfDictionary localState)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
|
|
||||||
@@ -248,8 +258,8 @@ public class IntroduceeEngine
|
|||||||
m.put(TYPE, TYPE_ACK);
|
m.put(TYPE, TYPE_ACK);
|
||||||
m.put(GROUP_ID, localState.getRaw(GROUP_ID));
|
m.put(GROUP_ID, localState.getRaw(GROUP_ID));
|
||||||
m.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
m.put(SESSION_ID, localState.getRaw(SESSION_ID));
|
||||||
m.put(MAC, localState.getRaw(MAC));
|
m.put(MAC, localState.getRaw(OUR_MAC));
|
||||||
m.put(SIGNATURE, localState.getRaw(SIGNATURE));
|
m.put(SIGNATURE, localState.getRaw(OUR_SIGNATURE));
|
||||||
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Sending ACK " + " to " +
|
LOG.info("Sending ACK " + " to " +
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.briarproject.api.crypto.KeyParser;
|
|||||||
import org.briarproject.api.crypto.PrivateKey;
|
import org.briarproject.api.crypto.PrivateKey;
|
||||||
import org.briarproject.api.crypto.PublicKey;
|
import org.briarproject.api.crypto.PublicKey;
|
||||||
import org.briarproject.api.crypto.SecretKey;
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.crypto.Signature;
|
||||||
import org.briarproject.api.data.BdfDictionary;
|
import org.briarproject.api.data.BdfDictionary;
|
||||||
import org.briarproject.api.data.BdfList;
|
import org.briarproject.api.data.BdfList;
|
||||||
import org.briarproject.api.db.DatabaseComponent;
|
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.Author;
|
||||||
import org.briarproject.api.identity.AuthorFactory;
|
import org.briarproject.api.identity.AuthorFactory;
|
||||||
import org.briarproject.api.identity.AuthorId;
|
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.TransportProperties;
|
||||||
import org.briarproject.api.properties.TransportPropertyManager;
|
import org.briarproject.api.properties.TransportPropertyManager;
|
||||||
import org.briarproject.api.sync.GroupId;
|
import org.briarproject.api.sync.GroupId;
|
||||||
@@ -32,6 +35,7 @@ import org.briarproject.api.system.Clock;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Logger;
|
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.INTRODUCER;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID;
|
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;
|
||||||
|
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_ID;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.NAME;
|
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.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_PRIVATE_KEY;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.OUR_PUBLIC_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_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.PUBLIC_KEY;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
|
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.REMOTE_AUTHOR_IS_US;
|
||||||
@@ -92,6 +101,7 @@ class IntroduceeManager {
|
|||||||
private final TransportPropertyManager transportPropertyManager;
|
private final TransportPropertyManager transportPropertyManager;
|
||||||
private final AuthorFactory authorFactory;
|
private final AuthorFactory authorFactory;
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
|
private final IdentityManager identityManager;
|
||||||
private final IntroductionGroupFactory introductionGroupFactory;
|
private final IntroductionGroupFactory introductionGroupFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -100,6 +110,7 @@ class IntroduceeManager {
|
|||||||
CryptoComponent cryptoComponent,
|
CryptoComponent cryptoComponent,
|
||||||
TransportPropertyManager transportPropertyManager,
|
TransportPropertyManager transportPropertyManager,
|
||||||
AuthorFactory authorFactory, ContactManager contactManager,
|
AuthorFactory authorFactory, ContactManager contactManager,
|
||||||
|
IdentityManager identityManager,
|
||||||
IntroductionGroupFactory introductionGroupFactory) {
|
IntroductionGroupFactory introductionGroupFactory) {
|
||||||
|
|
||||||
this.messageSender = messageSender;
|
this.messageSender = messageSender;
|
||||||
@@ -110,6 +121,7 @@ class IntroduceeManager {
|
|||||||
this.transportPropertyManager = transportPropertyManager;
|
this.transportPropertyManager = transportPropertyManager;
|
||||||
this.authorFactory = authorFactory;
|
this.authorFactory = authorFactory;
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
|
this.identityManager = identityManager;
|
||||||
this.introductionGroupFactory = introductionGroupFactory;
|
this.introductionGroupFactory = introductionGroupFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,18 +201,19 @@ class IntroduceeManager {
|
|||||||
byte[] privateKey = keyPair.getPrivate().getEncoded();
|
byte[] privateKey = keyPair.getPrivate().getEncoded();
|
||||||
Map<TransportId, TransportProperties> transportProperties =
|
Map<TransportId, TransportProperties> transportProperties =
|
||||||
transportPropertyManager.getLocalProperties(txn);
|
transportPropertyManager.getLocalProperties(txn);
|
||||||
|
BdfDictionary tp = encodeTransportProperties(transportProperties);
|
||||||
|
|
||||||
// update session state for later
|
// update session state for later
|
||||||
state.put(ACCEPT, true);
|
state.put(ACCEPT, true);
|
||||||
state.put(OUR_TIME, now);
|
state.put(OUR_TIME, now);
|
||||||
state.put(OUR_PUBLIC_KEY, publicKey);
|
state.put(OUR_PUBLIC_KEY, publicKey);
|
||||||
state.put(OUR_PRIVATE_KEY, privateKey);
|
state.put(OUR_PRIVATE_KEY, privateKey);
|
||||||
|
state.put(OUR_TRANSPORT, tp);
|
||||||
|
|
||||||
// define action
|
// define action
|
||||||
BdfDictionary localAction = new BdfDictionary();
|
BdfDictionary localAction = new BdfDictionary();
|
||||||
localAction.put(TYPE, TYPE_RESPONSE);
|
localAction.put(TYPE, TYPE_RESPONSE);
|
||||||
localAction.put(TRANSPORT,
|
localAction.put(TRANSPORT, tp);
|
||||||
encodeTransportProperties(transportProperties));
|
|
||||||
localAction.put(MESSAGE_TIME, timestamp);
|
localAction.put(MESSAGE_TIME, timestamp);
|
||||||
|
|
||||||
// start engine and process its state update
|
// start engine and process its state update
|
||||||
@@ -311,24 +324,59 @@ class IntroduceeManager {
|
|||||||
secretKey = cryptoComponent
|
secretKey = cryptoComponent
|
||||||
.deriveMasterSecret(theirEphemeralKey, keyPair, alice);
|
.deriveMasterSecret(theirEphemeralKey, keyPair, alice);
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
if (LOG.isLoggable(WARNING))
|
|
||||||
LOG.log(WARNING, e.toString(), e);
|
|
||||||
// we can not continue without the shared secret
|
// we can not continue without the shared secret
|
||||||
throw new FormatException();
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO MAC and signature
|
// Derive two nonces and a MAC key from the secret master key
|
||||||
localState.put(MAC, new byte[42]);
|
byte[] ourNonce =
|
||||||
localState.put(SIGNATURE, new byte[42]);
|
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
|
// The agreed timestamp is the minimum of the peers' timestamps
|
||||||
long ourTime = localState.getLong(OUR_TIME);
|
long ourTime = localState.getLong(OUR_TIME);
|
||||||
long theirTime = localState.getLong(TIME);
|
long theirTime = localState.getLong(TIME);
|
||||||
long timestamp = Math.min(ourTime, theirTime);
|
long timestamp = Math.min(ourTime, theirTime);
|
||||||
|
|
||||||
// Add the contact to the database
|
// Calculate a MAC over identity public key, ephemeral public key,
|
||||||
AuthorId localAuthorId =
|
// transport properties and timestamp.
|
||||||
new AuthorId(localState.getRaw(LOCAL_AUTHOR_ID));
|
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
|
Author remoteAuthor = authorFactory
|
||||||
.createAuthor(localState.getString(NAME),
|
.createAuthor(localState.getString(NAME),
|
||||||
localState.getRaw(PUBLIC_KEY));
|
localState.getRaw(PUBLIC_KEY));
|
||||||
@@ -365,13 +413,57 @@ class IntroduceeManager {
|
|||||||
if (!localState.getBoolean(EXISTS) &&
|
if (!localState.getBoolean(EXISTS) &&
|
||||||
localState.containsKey(ADDED_CONTACT_ID)) {
|
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...");
|
LOG.info("Activating Contact...");
|
||||||
|
|
||||||
ContactId contactId = new ContactId(
|
ContactId contactId = new ContactId(
|
||||||
localState.getLong(ADDED_CONTACT_ID).intValue());
|
localState.getLong(ADDED_CONTACT_ID).intValue());
|
||||||
|
|
||||||
// activate and show contact in contact list
|
// activate and show contact in contact list
|
||||||
db.setContactActive(txn, contactId, true);
|
contactManager.setContactActive(txn, contactId, true);
|
||||||
|
|
||||||
// broadcast event informing of successful introduction
|
// broadcast event informing of successful introduction
|
||||||
Contact contact = db.getContact(txn, contactId);
|
Contact contact = db.getContact(txn, contactId);
|
||||||
@@ -389,7 +481,7 @@ class IntroduceeManager {
|
|||||||
LOG.info("Deleting added contact due to abort...");
|
LOG.info("Deleting added contact due to abort...");
|
||||||
ContactId contactId = new ContactId(
|
ContactId contactId = new ContactId(
|
||||||
localState.getLong(ADDED_CONTACT_ID).intValue());
|
localState.getLong(ADDED_CONTACT_ID).intValue());
|
||||||
contactManager.removeContact(contactId);
|
contactManager.removeContact(txn, contactId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.E_PUBLIC_KEY;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
|
import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.MAC;
|
import static org.briarproject.api.introduction.IntroductionConstants.MAC;
|
||||||
|
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_ID;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.MSG;
|
import static org.briarproject.api.introduction.IntroductionConstants.MSG;
|
||||||
@@ -157,7 +158,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
|||||||
checkSize(message, 4);
|
checkSize(message, 4);
|
||||||
|
|
||||||
byte[] mac = message.getRaw(2);
|
byte[] mac = message.getRaw(2);
|
||||||
// TODO length check?
|
checkLength(mac, 1, MAC_LENGTH);
|
||||||
|
|
||||||
byte[] sig = message.getRaw(3);
|
byte[] sig = message.getRaw(3);
|
||||||
checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
|
checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.briarproject.TestUtils;
|
|||||||
import org.briarproject.api.Bytes;
|
import org.briarproject.api.Bytes;
|
||||||
import org.briarproject.api.FormatException;
|
import org.briarproject.api.FormatException;
|
||||||
import org.briarproject.api.clients.ClientHelper;
|
import org.briarproject.api.clients.ClientHelper;
|
||||||
|
import org.briarproject.api.clients.SessionId;
|
||||||
import org.briarproject.api.contact.Contact;
|
import org.briarproject.api.contact.Contact;
|
||||||
import org.briarproject.api.contact.ContactId;
|
import org.briarproject.api.contact.ContactId;
|
||||||
import org.briarproject.api.contact.ContactManager;
|
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.Author;
|
||||||
import org.briarproject.api.identity.AuthorFactory;
|
import org.briarproject.api.identity.AuthorFactory;
|
||||||
import org.briarproject.api.identity.AuthorId;
|
import org.briarproject.api.identity.AuthorId;
|
||||||
|
import org.briarproject.api.identity.IdentityManager;
|
||||||
import org.briarproject.api.introduction.IntroduceeProtocolState;
|
import org.briarproject.api.introduction.IntroduceeProtocolState;
|
||||||
import org.briarproject.api.clients.SessionId;
|
|
||||||
import org.briarproject.api.properties.TransportPropertyManager;
|
import org.briarproject.api.properties.TransportPropertyManager;
|
||||||
import org.briarproject.api.sync.ClientId;
|
import org.briarproject.api.sync.ClientId;
|
||||||
import org.briarproject.api.sync.Group;
|
import org.briarproject.api.sync.Group;
|
||||||
@@ -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_REQUEST;
|
||||||
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||||
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
public class IntroduceeManagerTest extends BriarTestCase {
|
public class IntroduceeManagerTest extends BriarTestCase {
|
||||||
|
|
||||||
final Mockery context;
|
private final Mockery context;
|
||||||
final IntroduceeManager introduceeManager;
|
private final IntroduceeManager introduceeManager;
|
||||||
final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
final CryptoComponent cryptoComponent;
|
private final CryptoComponent cryptoComponent;
|
||||||
final ClientHelper clientHelper;
|
private final ClientHelper clientHelper;
|
||||||
final IntroductionGroupFactory introductionGroupFactory;
|
private final IntroductionGroupFactory introductionGroupFactory;
|
||||||
final MessageSender messageSender;
|
private final MessageSender messageSender;
|
||||||
final TransportPropertyManager transportPropertyManager;
|
private final TransportPropertyManager transportPropertyManager;
|
||||||
final AuthorFactory authorFactory;
|
private final AuthorFactory authorFactory;
|
||||||
final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
final Clock clock;
|
private final IdentityManager identityManager;
|
||||||
final Contact introducer;
|
private final Clock clock;
|
||||||
final Contact introducee1;
|
private final Contact introducer;
|
||||||
final Contact introducee2;
|
private final Contact introducee1;
|
||||||
final Group localGroup1;
|
private final Contact introducee2;
|
||||||
final Group introductionGroup1;
|
private final Group localGroup1;
|
||||||
final Group introductionGroup2;
|
private final Group introductionGroup1;
|
||||||
final Transaction txn;
|
private final Transaction txn;
|
||||||
final long time = 42L;
|
private final long time = 42L;
|
||||||
final Message localStateMessage;
|
private final Message localStateMessage;
|
||||||
final ClientId clientId;
|
private final ClientId clientId;
|
||||||
final SessionId sessionId;
|
private final SessionId sessionId;
|
||||||
final Message message1;
|
private final Message message1;
|
||||||
|
|
||||||
public IntroduceeManagerTest() {
|
public IntroduceeManagerTest() {
|
||||||
context = new Mockery();
|
context = new Mockery();
|
||||||
@@ -105,10 +105,12 @@ public class IntroduceeManagerTest extends BriarTestCase {
|
|||||||
transportPropertyManager = context.mock(TransportPropertyManager.class);
|
transportPropertyManager = context.mock(TransportPropertyManager.class);
|
||||||
authorFactory = context.mock(AuthorFactory.class);
|
authorFactory = context.mock(AuthorFactory.class);
|
||||||
contactManager = context.mock(ContactManager.class);
|
contactManager = context.mock(ContactManager.class);
|
||||||
|
identityManager = context.mock(IdentityManager.class);
|
||||||
|
|
||||||
introduceeManager = new IntroduceeManager(messageSender, db,
|
introduceeManager = new IntroduceeManager(messageSender, db,
|
||||||
clientHelper, clock, cryptoComponent, transportPropertyManager,
|
clientHelper, clock, cryptoComponent, transportPropertyManager,
|
||||||
authorFactory, contactManager, introductionGroupFactory);
|
authorFactory, contactManager, identityManager,
|
||||||
|
introductionGroupFactory);
|
||||||
|
|
||||||
AuthorId authorId0 = new AuthorId(TestUtils.getRandomId());
|
AuthorId authorId0 = new AuthorId(TestUtils.getRandomId());
|
||||||
Author author0 = new Author(authorId0, "Introducer",
|
Author author0 = new Author(authorId0, "Introducer",
|
||||||
@@ -138,8 +140,6 @@ public class IntroduceeManagerTest extends BriarTestCase {
|
|||||||
clientId, new byte[0]);
|
clientId, new byte[0]);
|
||||||
introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
|
introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
|
||||||
clientId, new byte[0]);
|
clientId, new byte[0]);
|
||||||
introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()),
|
|
||||||
clientId, new byte[0]);
|
|
||||||
|
|
||||||
sessionId = new SessionId(TestUtils.getRandomId());
|
sessionId = new SessionId(TestUtils.getRandomId());
|
||||||
localStateMessage = new Message(
|
localStateMessage = new Message(
|
||||||
|
|||||||
Reference in New Issue
Block a user