Replace old introduction client with new one

This commit is contained in:
Torsten Grote
2018-04-23 17:11:10 -03:00
parent 1bc29fec06
commit f81ef30b47
80 changed files with 1449 additions and 5924 deletions

View File

@@ -11,7 +11,6 @@ import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
@@ -35,7 +34,7 @@ import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
public class IntroductionMessageFragment extends BaseFragment public class IntroductionMessageFragment extends BaseFragment
implements TextInputListener { implements TextInputListener {
@@ -175,7 +174,7 @@ public class IntroductionMessageFragment extends BaseFragment
ui.message.setSendButtonEnabled(false); ui.message.setSendButtonEnabled(false);
String msg = ui.message.getText().toString(); String msg = ui.message.getText().toString();
msg = StringUtils.truncateUtf8(msg, MAX_INTRODUCTION_MESSAGE_LENGTH); msg = StringUtils.truncateUtf8(msg, MAX_REQUEST_MESSAGE_LENGTH);
makeIntroduction(contact1, contact2, msg); makeIntroduction(contact1, contact2, msg);
// don't wait for the introduction to be made before finishing activity // don't wait for the introduction to be made before finishing activity
@@ -190,7 +189,7 @@ public class IntroductionMessageFragment extends BaseFragment
try { try {
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
introductionManager.makeIntroduction(c1, c2, msg, timestamp); introductionManager.makeIntroduction(c1, c2, msg, timestamp);
} catch (DbException | FormatException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
introductionError(); introductionError();
} }

View File

@@ -1,53 +0,0 @@
package org.briarproject.briar.api.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
@NotNullByDefault
public enum IntroduceeAction {
LOCAL_ACCEPT,
LOCAL_DECLINE,
LOCAL_ABORT,
REMOTE_REQUEST,
REMOTE_ACCEPT,
REMOTE_DECLINE,
REMOTE_ABORT,
ACK;
@Nullable
public static IntroduceeAction getRemote(int type, boolean accept) {
if (type == TYPE_REQUEST) return REMOTE_REQUEST;
if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT;
if (type == TYPE_RESPONSE) return REMOTE_DECLINE;
if (type == TYPE_ACK) return ACK;
if (type == TYPE_ABORT) return REMOTE_ABORT;
return null;
}
@Nullable
public static IntroduceeAction getRemote(int type) {
return getRemote(type, true);
}
@Nullable
public static IntroduceeAction getLocal(int type, boolean accept) {
if (type == TYPE_RESPONSE && accept) return LOCAL_ACCEPT;
if (type == TYPE_RESPONSE) return LOCAL_DECLINE;
if (type == TYPE_ACK) return ACK;
if (type == TYPE_ABORT) return LOCAL_ABORT;
return null;
}
@Nullable
public static IntroduceeAction getLocal(int type) {
return getLocal(type, true);
}
}

View File

@@ -1,82 +0,0 @@
package org.briarproject.briar.api.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.introduction.IntroduceeAction.ACK;
import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_ACCEPT;
import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_DECLINE;
import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_ACCEPT;
import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_DECLINE;
import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_REQUEST;
@Immutable
@NotNullByDefault
public enum IntroduceeProtocolState {
ERROR(0),
AWAIT_REQUEST(1) {
@Override
public IntroduceeProtocolState next(IntroduceeAction a) {
if (a == REMOTE_REQUEST) return AWAIT_RESPONSES;
return ERROR;
}
},
AWAIT_RESPONSES(2) {
@Override
public IntroduceeProtocolState next(IntroduceeAction a) {
if (a == REMOTE_ACCEPT) return AWAIT_LOCAL_RESPONSE;
if (a == REMOTE_DECLINE) return FINISHED;
if (a == LOCAL_ACCEPT) return AWAIT_REMOTE_RESPONSE;
if (a == LOCAL_DECLINE) return FINISHED;
return ERROR;
}
},
AWAIT_REMOTE_RESPONSE(3) {
@Override
public IntroduceeProtocolState next(IntroduceeAction a) {
if (a == REMOTE_ACCEPT) return AWAIT_ACK;
if (a == REMOTE_DECLINE) return FINISHED;
return ERROR;
}
},
AWAIT_LOCAL_RESPONSE(4) {
@Override
public IntroduceeProtocolState next(IntroduceeAction a) {
if (a == LOCAL_ACCEPT) return AWAIT_ACK;
if (a == LOCAL_DECLINE) return FINISHED;
return ERROR;
}
},
AWAIT_ACK(5) {
@Override
public IntroduceeProtocolState next(IntroduceeAction a) {
if (a == ACK) return FINISHED;
return ERROR;
}
},
FINISHED(6);
private final int value;
IntroduceeProtocolState(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static IntroduceeProtocolState fromValue(int value) {
for (IntroduceeProtocolState s : values()) {
if (s.value == value) return s;
}
throw new IllegalArgumentException();
}
public IntroduceeProtocolState next(IntroduceeAction a) {
return this;
}
}

View File

@@ -1,54 +0,0 @@
package org.briarproject.briar.api.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
@NotNullByDefault
public enum IntroducerAction {
LOCAL_REQUEST,
LOCAL_ABORT,
REMOTE_ACCEPT_1,
REMOTE_ACCEPT_2,
REMOTE_DECLINE_1,
REMOTE_DECLINE_2,
REMOTE_ABORT,
ACK_1,
ACK_2;
@Nullable
public static IntroducerAction getLocal(int type) {
if (type == TYPE_REQUEST) return LOCAL_REQUEST;
if (type == TYPE_ABORT) return LOCAL_ABORT;
return null;
}
@Nullable
public static IntroducerAction getRemote(int type, boolean one,
boolean accept) {
if (one) {
if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT_1;
if (type == TYPE_RESPONSE) return REMOTE_DECLINE_1;
if (type == TYPE_ACK) return ACK_1;
} else {
if (type == TYPE_RESPONSE && accept) return REMOTE_ACCEPT_2;
if (type == TYPE_RESPONSE) return REMOTE_DECLINE_2;
if (type == TYPE_ACK) return ACK_2;
}
if (type == TYPE_ABORT) return REMOTE_ABORT;
return null;
}
@Nullable
public static IntroducerAction getRemote(int type, boolean one) {
return getRemote(type, one, true);
}
}

View File

@@ -1,108 +0,0 @@
package org.briarproject.briar.api.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.introduction.IntroducerAction.ACK_1;
import static org.briarproject.briar.api.introduction.IntroducerAction.ACK_2;
import static org.briarproject.briar.api.introduction.IntroducerAction.LOCAL_REQUEST;
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ABORT;
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_1;
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_2;
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_1;
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_2;
@Immutable
@NotNullByDefault
public enum IntroducerProtocolState {
ERROR(0),
PREPARE_REQUESTS(1) {
@Override
public IntroducerProtocolState next(IntroducerAction a) {
if (a == LOCAL_REQUEST) return AWAIT_RESPONSES;
return ERROR;
}
},
AWAIT_RESPONSES(2) {
@Override
public IntroducerProtocolState next(IntroducerAction a) {
if (a == REMOTE_ACCEPT_1) return AWAIT_RESPONSE_2;
if (a == REMOTE_ACCEPT_2) return AWAIT_RESPONSE_1;
if (a == REMOTE_DECLINE_1) return FINISHED;
if (a == REMOTE_DECLINE_2) return FINISHED;
return ERROR;
}
},
AWAIT_RESPONSE_1(3) {
@Override
public IntroducerProtocolState next(IntroducerAction a) {
if (a == REMOTE_ACCEPT_1) return AWAIT_ACKS;
if (a == REMOTE_DECLINE_1) return FINISHED;
return ERROR;
}
},
AWAIT_RESPONSE_2(4) {
@Override
public IntroducerProtocolState next(IntroducerAction a) {
if (a == REMOTE_ACCEPT_2) return AWAIT_ACKS;
if (a == REMOTE_DECLINE_2) return FINISHED;
return ERROR;
}
},
AWAIT_ACKS(5) {
@Override
public IntroducerProtocolState next(IntroducerAction a) {
if (a == ACK_1) return AWAIT_ACK_2;
if (a == ACK_2) return AWAIT_ACK_1;
return ERROR;
}
},
AWAIT_ACK_1(6) {
@Override
public IntroducerProtocolState next(IntroducerAction a) {
if (a == ACK_1) return FINISHED;
return ERROR;
}
},
AWAIT_ACK_2(7) {
@Override
public IntroducerProtocolState next(IntroducerAction a) {
if (a == ACK_2) return FINISHED;
return ERROR;
}
},
FINISHED(8) {
@Override
public IntroducerProtocolState next(IntroducerAction a) {
if (a == REMOTE_ABORT) return ERROR;
return FINISHED;
}
};
private final int value;
IntroducerProtocolState(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static IntroducerProtocolState fromValue(int value) {
for (IntroducerProtocolState s : values()) {
if (s.value == value) return s;
}
throw new IllegalArgumentException();
}
public static boolean isOngoing(IntroducerProtocolState state) {
return state != FINISHED && state != ERROR;
}
public IntroducerProtocolState next(IntroducerAction a) {
return this;
}
}

View File

@@ -4,126 +4,26 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_L
public interface IntroductionConstants { public interface IntroductionConstants {
/* Protocol roles */
int ROLE_INTRODUCER = 0;
int ROLE_INTRODUCEE = 1;
/* Message types */
int TYPE_REQUEST = 1;
int TYPE_RESPONSE = 2;
int TYPE_ACK = 3;
int TYPE_ABORT = 4;
/* Message Constants */
String TYPE = "type";
String GROUP_ID = "groupId";
String SESSION_ID = "sessionId";
String CONTACT = "contactId";
String NAME = "name";
String PUBLIC_KEY = "publicKey";
String E_PUBLIC_KEY = "ephemeralPublicKey";
String MSG = "msg";
String ACCEPT = "accept";
String TIME = "time";
String TRANSPORT = "transport";
String MESSAGE_ID = "messageId";
String MESSAGE_TIME = "timestamp";
String MAC = "mac";
String SIGNATURE = "signature";
/* Validation Constants */
/**
* The length of the message authentication code in bytes.
*/
int MAC_LENGTH = 32;
/** /**
* The maximum length of the introducer's optional message to the * The maximum length of the introducer's optional message to the
* introducees in UTF-8 bytes. * introducees in UTF-8 bytes.
*/ */
int MAX_INTRODUCTION_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; int MAX_REQUEST_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
/* Introducer Local State Metadata */ String LABEL_SESSION_ID = "org.briarproject.briar.introduction/SESSION_ID";
String STATE = "state";
String ROLE = "role";
String GROUP_ID_1 = "groupId1";
String GROUP_ID_2 = "groupId2";
String CONTACT_1 = "contact1";
String CONTACT_2 = "contact2";
String AUTHOR_ID_1 = "authorId1";
String AUTHOR_ID_2 = "authorId2";
String CONTACT_ID_1 = "contactId1";
String CONTACT_ID_2 = "contactId2";
String RESPONSE_1 = "response1";
String RESPONSE_2 = "response2";
/* Introduction Request Action */ String LABEL_MASTER_KEY = "org.briarproject.briar.introduction/MASTER_KEY";
String PUBLIC_KEY1 = "publicKey1";
String PUBLIC_KEY2 = "publicKey2";
/* Introducee Local State Metadata (without those already defined) */ String LABEL_ALICE_MAC_KEY =
String STORAGE_ID = "storageId";
String INTRODUCER = "introducer";
String LOCAL_AUTHOR_ID = "localAuthorId";
String REMOTE_AUTHOR_ID = "remoteAuthorId";
String OUR_PUBLIC_KEY = "ourEphemeralPublicKey";
String OUR_PRIVATE_KEY = "ourEphemeralPrivateKey";
String OUR_TIME = "ourTime";
String ADDED_CONTACT_ID = "addedContactId";
String NOT_OUR_RESPONSE = "notOurResponse";
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;
int TASK_ACTIVATE_CONTACT = 1;
int TASK_ABORT = 2;
/**
* Label for deriving the shared secret.
*/
String SHARED_SECRET_LABEL =
"org.briarproject.briar.introduction/SHARED_SECRET";
/**
* Label for deriving Alice's key binding nonce from the shared secret.
*/
String ALICE_NONCE_LABEL =
"org.briarproject.briar.introduction/ALICE_NONCE";
/**
* Label for deriving Bob's key binding nonce from the shared secret.
*/
String BOB_NONCE_LABEL =
"org.briarproject.briar.introduction/BOB_NONCE";
/**
* Label for deriving Alice's MAC key from the shared secret.
*/
String ALICE_MAC_KEY_LABEL =
"org.briarproject.briar.introduction/ALICE_MAC_KEY"; "org.briarproject.briar.introduction/ALICE_MAC_KEY";
/** String LABEL_BOB_MAC_KEY =
* Label for deriving Bob's MAC key from the shared secret.
*/
String BOB_MAC_KEY_LABEL =
"org.briarproject.briar.introduction/BOB_MAC_KEY"; "org.briarproject.briar.introduction/BOB_MAC_KEY";
/** String LABEL_AUTH_MAC = "org.briarproject.briar.introduction/AUTH_MAC";
* Label for signing the introduction response.
*/ String LABEL_AUTH_SIGN = "org.briarproject.briar.introduction/AUTH_SIGN";
String SIGNING_LABEL =
"org.briarproject.briar.introduction/RESPONSE_SIGNATURE"; String LABEL_AUTH_NONCE = "org.briarproject.briar.introduction/AUTH_NONCE";
/**
* Label for MACing the introduction response.
*/
String MAC_LABEL = "org.briarproject.briar.introduction/RESPONSE_MAC";
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.api.introduction; package org.briarproject.briar.api.introduction;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -24,25 +23,25 @@ public interface IntroductionManager extends ConversationClient {
/** /**
* The current version of the introduction client. * The current version of the introduction client.
*/ */
int CLIENT_VERSION = 0; int CLIENT_VERSION = 1;
/** /**
* Sends two initial introduction messages. * Sends two initial introduction messages.
*/ */
void makeIntroduction(Contact c1, Contact c2, @Nullable String msg, void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
long timestamp) throws DbException, FormatException; long timestamp) throws DbException;
/** /**
* Accepts an introduction. * Accepts an introduction.
*/ */
void acceptIntroduction(ContactId contactId, SessionId sessionId, void acceptIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException, FormatException; long timestamp) throws DbException;
/** /**
* Declines an introduction. * Declines an introduction.
*/ */
void declineIntroduction(ContactId contactId, SessionId sessionId, void declineIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException, FormatException; long timestamp) throws DbException;
/** /**
* Returns all introduction messages for the given contact. * Returns all introduction messages for the given contact.

View File

@@ -8,7 +8,7 @@ import org.briarproject.briar.api.client.SessionId;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER; import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -16,10 +16,10 @@ public class IntroductionMessage extends BaseMessageHeader {
private final SessionId sessionId; private final SessionId sessionId;
private final MessageId messageId; private final MessageId messageId;
private final int role; private final Role role;
IntroductionMessage(SessionId sessionId, MessageId messageId, IntroductionMessage(SessionId sessionId, MessageId messageId,
GroupId groupId, int role, long time, boolean local, boolean sent, GroupId groupId, Role role, long time, boolean local, boolean sent,
boolean seen, boolean read) { boolean seen, boolean read) {
super(messageId, groupId, time, local, sent, seen, read); super(messageId, groupId, time, local, sent, seen, read);
@@ -37,7 +37,7 @@ public class IntroductionMessage extends BaseMessageHeader {
} }
public boolean isIntroducer() { public boolean isIntroducer() {
return role == ROLE_INTRODUCER; return role == INTRODUCER;
} }
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.api.introduction; package org.briarproject.briar.api.introduction;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
@@ -15,21 +14,19 @@ public class IntroductionRequest extends IntroductionResponse {
@Nullable @Nullable
private final String message; private final String message;
private final boolean answered, exists, introducesOtherIdentity; private final boolean answered, exists;
public IntroductionRequest(SessionId sessionId, MessageId messageId, public IntroductionRequest(SessionId sessionId, MessageId messageId,
GroupId groupId, int role, long time, boolean local, boolean sent, GroupId groupId, Role role, long time, boolean local, boolean sent,
boolean seen, boolean read, AuthorId authorId, String name, boolean seen, boolean read, String name, boolean accepted,
boolean accepted, @Nullable String message, boolean answered, @Nullable String message, boolean answered, boolean exists) {
boolean exists, boolean introducesOtherIdentity) {
super(sessionId, messageId, groupId, role, time, local, sent, seen, super(sessionId, messageId, groupId, role, time, local, sent, seen,
read, authorId, name, accepted); read, name, accepted);
this.message = message; this.message = message;
this.answered = answered; this.answered = answered;
this.exists = exists; this.exists = exists;
this.introducesOtherIdentity = introducesOtherIdentity;
} }
@Nullable @Nullable
@@ -44,8 +41,4 @@ public class IntroductionRequest extends IntroductionResponse {
public boolean contactExists() { public boolean contactExists() {
return exists; return exists;
} }
public boolean doesIntroduceOtherIdentity() {
return introducesOtherIdentity;
}
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.api.introduction; package org.briarproject.briar.api.introduction;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
@@ -12,19 +11,15 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class IntroductionResponse extends IntroductionMessage { public class IntroductionResponse extends IntroductionMessage {
private final AuthorId remoteAuthorId;
private final String name; private final String name;
private final boolean accepted; private final boolean accepted;
public IntroductionResponse(SessionId sessionId, MessageId messageId, public IntroductionResponse(SessionId sessionId, MessageId messageId,
GroupId groupId, int role, long time, boolean local, boolean sent, GroupId groupId, Role role, long time, boolean local, boolean sent,
boolean seen, boolean read, AuthorId remoteAuthorId, String name, boolean seen, boolean read, String name, boolean accepted) {
boolean accepted) {
super(sessionId, messageId, groupId, role, time, local, sent, seen, super(sessionId, messageId, groupId, role, time, local, sent, seen,
read); read);
this.remoteAuthorId = remoteAuthorId;
this.name = name; this.name = name;
this.accepted = accepted; this.accepted = accepted;
} }
@@ -37,7 +32,4 @@ public class IntroductionResponse extends IntroductionMessage {
return accepted; return accepted;
} }
public AuthorId getRemoteAuthorId() {
return remoteAuthorId;
}
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.api.introduction2; package org.briarproject.briar.api.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.api.introduction.event; package org.briarproject.briar.api.introduction.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
@@ -11,19 +10,14 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class IntroductionAbortedEvent extends Event { public class IntroductionAbortedEvent extends Event {
private final ContactId contactId;
private final SessionId sessionId; private final SessionId sessionId;
public IntroductionAbortedEvent(ContactId contactId, SessionId sessionId) { public IntroductionAbortedEvent(SessionId sessionId) {
this.contactId = contactId;
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public ContactId getContactId() {
return contactId;
}
public SessionId getSessionId() { public SessionId getSessionId() {
return sessionId; return sessionId;
} }
} }

View File

@@ -8,6 +8,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
// TODO still needed?
public class IntroductionSucceededEvent extends Event { public class IntroductionSucceededEvent extends Event {
private final Contact contact; private final Contact contact;

View File

@@ -1,29 +0,0 @@
package org.briarproject.briar.api.introduction2;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
public interface IntroductionConstants {
/**
* The maximum length of the introducer's optional message to the
* introducees in UTF-8 bytes.
*/
int MAX_REQUEST_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
String LABEL_SESSION_ID = "org.briarproject.briar.introduction/SESSION_ID";
String LABEL_MASTER_KEY = "org.briarproject.briar.introduction/MASTER_KEY";
String LABEL_ALICE_MAC_KEY =
"org.briarproject.briar.introduction/ALICE_MAC_KEY";
String LABEL_BOB_MAC_KEY =
"org.briarproject.briar.introduction/BOB_MAC_KEY";
String LABEL_AUTH_MAC = "org.briarproject.briar.introduction/AUTH_MAC";
String LABEL_AUTH_SIGN = "org.briarproject.briar.introduction/AUTH_SIGN";
String LABEL_AUTH_NONCE = "org.briarproject.briar.introduction/AUTH_NONCE";
}

View File

@@ -1,52 +0,0 @@
package org.briarproject.briar.api.introduction2;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.messaging.ConversationManager.ConversationClient;
import java.util.Collection;
import javax.annotation.Nullable;
@NotNullByDefault
public interface IntroductionManager extends ConversationClient {
/**
* The unique ID of the introduction client.
*/
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.introduction");
/**
* The current version of the introduction client.
*/
int CLIENT_VERSION = 1;
/**
* Sends two initial introduction messages.
*/
void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
long timestamp) throws DbException;
/**
* Accepts an introduction.
*/
void acceptIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException;
/**
* Declines an introduction.
*/
void declineIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException;
/**
* Returns all introduction messages for the given contact.
*/
Collection<IntroductionMessage> getIntroductionMessages(ContactId contactId)
throws DbException;
}

View File

@@ -1,43 +0,0 @@
package org.briarproject.briar.api.introduction2;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.BaseMessageHeader;
import org.briarproject.briar.api.client.SessionId;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
@Immutable
@NotNullByDefault
public class IntroductionMessage extends BaseMessageHeader {
private final SessionId sessionId;
private final MessageId messageId;
private final Role role;
IntroductionMessage(SessionId sessionId, MessageId messageId,
GroupId groupId, Role role, long time, boolean local, boolean sent,
boolean seen, boolean read) {
super(messageId, groupId, time, local, sent, seen, read);
this.sessionId = sessionId;
this.messageId = messageId;
this.role = role;
}
public SessionId getSessionId() {
return sessionId;
}
public MessageId getMessageId() {
return messageId;
}
public boolean isIntroducer() {
return role == INTRODUCER;
}
}

View File

@@ -1,44 +0,0 @@
package org.briarproject.briar.api.introduction2;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class IntroductionRequest extends IntroductionResponse {
@Nullable
private final String message;
private final boolean answered, exists;
public IntroductionRequest(SessionId sessionId, MessageId messageId,
GroupId groupId, Role role, long time, boolean local, boolean sent,
boolean seen, boolean read, String name, boolean accepted,
@Nullable String message, boolean answered, boolean exists) {
super(sessionId, messageId, groupId, role, time, local, sent, seen,
read, name, accepted);
this.message = message;
this.answered = answered;
this.exists = exists;
}
@Nullable
public String getMessage() {
return message;
}
public boolean wasAnswered() {
return answered;
}
public boolean contactExists() {
return exists;
}
}

View File

@@ -1,35 +0,0 @@
package org.briarproject.briar.api.introduction2;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class IntroductionResponse extends IntroductionMessage {
private final String name;
private final boolean accepted;
public IntroductionResponse(SessionId sessionId, MessageId messageId,
GroupId groupId, Role role, long time, boolean local, boolean sent,
boolean seen, boolean read, String name, boolean accepted) {
super(sessionId, messageId, groupId, role, time, local, sent, seen,
read);
this.name = name;
this.accepted = accepted;
}
public String getName() {
return name;
}
public boolean wasAccepted() {
return accepted;
}
}

View File

@@ -1,32 +0,0 @@
package org.briarproject.briar.api.introduction2.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.client.SessionId;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
// TODO still needed?
public class IntroductionAbortedEvent extends Event {
private final AuthorId remoteAuthorId;
private final SessionId sessionId;
public IntroductionAbortedEvent(AuthorId remoteAuthorId,
SessionId sessionId) {
this.remoteAuthorId = remoteAuthorId;
this.sessionId = sessionId;
}
public AuthorId getRemoteAuthorId() {
return remoteAuthorId;
}
public SessionId getSessionId() {
return sessionId;
}
}

View File

@@ -1,32 +0,0 @@
package org.briarproject.briar.api.introduction2.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.introduction2.IntroductionRequest;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class IntroductionRequestReceivedEvent extends Event {
private final ContactId contactId;
private final IntroductionRequest introductionRequest;
public IntroductionRequestReceivedEvent(ContactId contactId,
IntroductionRequest introductionRequest) {
this.contactId = contactId;
this.introductionRequest = introductionRequest;
}
public ContactId getContactId() {
return contactId;
}
public IntroductionRequest getIntroductionRequest() {
return introductionRequest;
}
}

View File

@@ -1,31 +0,0 @@
package org.briarproject.briar.api.introduction2.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.introduction2.IntroductionResponse;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class IntroductionResponseReceivedEvent extends Event {
private final ContactId contactId;
private final IntroductionResponse introductionResponse;
public IntroductionResponseReceivedEvent(ContactId contactId,
IntroductionResponse introductionResponse) {
this.contactId = contactId;
this.introductionResponse = introductionResponse;
}
public ContactId getContactId() {
return contactId;
}
public IntroductionResponse getIntroductionResponse() {
return introductionResponse;
}
}

View File

@@ -1,23 +0,0 @@
package org.briarproject.briar.api.introduction2.event;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
// TODO still needed?
public class IntroductionSucceededEvent extends Event {
private final Contact contact;
public IntroductionSucceededEvent(Contact contact) {
this.contact = contact;
}
public Contact getContact() {
return contact;
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class AbortMessage extends IntroductionMessage { class AbortMessage extends AbstractIntroductionMessage {
private final SessionId sessionId; private final SessionId sessionId;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
abstract class IntroductionMessage { abstract class AbstractIntroductionMessage {
private final MessageId messageId; private final MessageId messageId;
private final GroupId groupId; private final GroupId groupId;
@@ -17,7 +17,7 @@ abstract class IntroductionMessage {
@Nullable @Nullable
private final MessageId previousMessageId; private final MessageId previousMessageId;
IntroductionMessage(MessageId messageId, GroupId groupId, AbstractIntroductionMessage(MessageId messageId, GroupId groupId,
long timestamp, @Nullable MessageId previousMessageId) { long timestamp, @Nullable MessageId previousMessageId) {
this.messageId = messageId; this.messageId = messageId;
this.groupId = groupId; this.groupId = groupId;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
@@ -25,14 +25,14 @@ import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_ID; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
import static org.briarproject.briar.introduction2.MessageType.ABORT; import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.introduction2.MessageType.ACCEPT; import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction2.MessageType.ACTIVATE; import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
import static org.briarproject.briar.introduction2.MessageType.AUTH; import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.introduction2.MessageType.DECLINE; import static org.briarproject.briar.introduction.MessageType.DECLINE;
import static org.briarproject.briar.introduction2.MessageType.REQUEST; import static org.briarproject.briar.introduction.MessageType.REQUEST;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
@@ -14,7 +14,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class AcceptMessage extends IntroductionMessage { class AcceptMessage extends AbstractIntroductionMessage {
private final SessionId sessionId; private final SessionId sessionId;
private final byte[] ephemeralPublicKey; private final byte[] ephemeralPublicKey;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class ActivateMessage extends IntroductionMessage { class ActivateMessage extends AbstractIntroductionMessage {
private final SessionId sessionId; private final SessionId sessionId;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class AuthMessage extends IntroductionMessage { class AuthMessage extends AbstractIntroductionMessage {
private final SessionId sessionId; private final SessionId sessionId;
private final byte[] mac, signature; private final byte[] mac, signature;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class DeclineMessage extends IntroductionMessage { class DeclineMessage extends AbstractIntroductionMessage {
private final SessionId sessionId; private final SessionId sessionId;

View File

@@ -1,372 +0,0 @@
package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.ProtocolEngine;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction.IntroduceeAction;
import org.briarproject.briar.api.introduction.IntroduceeProtocolState;
import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.briar.api.introduction.IntroduceeAction.ACK;
import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_ABORT;
import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_ACCEPT;
import static org.briarproject.briar.api.introduction.IntroduceeAction.LOCAL_DECLINE;
import static org.briarproject.briar.api.introduction.IntroduceeAction.REMOTE_ABORT;
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_ACK;
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REMOTE_RESPONSE;
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_RESPONSES;
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.ERROR;
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.FINISHED;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS;
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ACTIVATE_CONTACT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ADD_CONTACT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
@Immutable
@NotNullByDefault
class IntroduceeEngine
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
private static final Logger LOG =
Logger.getLogger(IntroduceeEngine.class.getName());
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
BdfDictionary localState, BdfDictionary localAction) {
try {
IntroduceeProtocolState currentState =
getState(localState.getLong(STATE));
int type = localAction.getLong(TYPE).intValue();
IntroduceeAction action;
if (localState.containsKey(ACCEPT)) action = IntroduceeAction
.getLocal(type, localState.getBoolean(ACCEPT));
else action = IntroduceeAction.getLocal(type);
IntroduceeProtocolState nextState = currentState.next(action);
if (action == LOCAL_ABORT && currentState != ERROR) {
return abortSession(currentState, localState);
}
if (nextState == ERROR) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Error: Invalid action in state " +
currentState.name());
}
if (currentState == ERROR) return noUpdate(localState);
else return abortSession(currentState, localState);
}
List<BdfDictionary> messages = new ArrayList<>(1);
if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) {
localState.put(STATE, nextState.getValue());
localState.put(ANSWERED, true);
// create the introduction response message
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_RESPONSE);
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(ACCEPT, localState.getBoolean(ACCEPT));
if (localState.getBoolean(ACCEPT)) {
msg.put(TIME, localState.getLong(OUR_TIME));
msg.put(E_PUBLIC_KEY, localState.getRaw(OUR_PUBLIC_KEY));
msg.put(TRANSPORT, localAction.getDictionary(TRANSPORT));
}
msg.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME));
messages.add(msg);
logAction(currentState, localState, msg);
if (nextState == AWAIT_ACK) {
localState.put(TASK, TASK_ADD_CONTACT);
}
} else if (action == ACK) {
// just send ACK, don't update local state again
BdfDictionary ack = getAckMessage(localState);
messages.add(ack);
} else {
throw new IllegalArgumentException();
}
List<Event> events = Collections.emptyList();
return new StateUpdate<>(false, false,
localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
BdfDictionary localState, BdfDictionary msg) {
try {
IntroduceeProtocolState currentState =
getState(localState.getLong(STATE));
int type = msg.getLong(TYPE).intValue();
IntroduceeAction action = IntroduceeAction.getRemote(type);
IntroduceeProtocolState nextState = currentState.next(action);
logMessageReceived(currentState, nextState, localState, type, msg);
if (nextState == ERROR) {
if (currentState != ERROR && action != REMOTE_ABORT) {
return abortSession(currentState, localState);
} else {
return noUpdate(localState);
}
}
// update local session state with next protocol state
localState.put(STATE, nextState.getValue());
List<BdfDictionary> messages;
List<Event> events;
// we received the introduction request
if (currentState == AWAIT_REQUEST) {
// remember the session ID used by the introducer
localState.put(SESSION_ID, msg.getRaw(SESSION_ID));
addRequestData(localState, msg);
messages = Collections.emptyList();
events = Collections.singletonList(getEvent(localState, msg));
}
// we had the request and now one response came in _OR_
// we had sent our response already and now received the other one
else if (currentState == AWAIT_RESPONSES ||
currentState == AWAIT_REMOTE_RESPONSE) {
// update next state based on message content
action = IntroduceeAction
.getRemote(type, msg.getBoolean(ACCEPT));
nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
addResponseData(localState, msg);
if (nextState == AWAIT_ACK) {
localState.put(TASK, TASK_ADD_CONTACT);
}
messages = Collections.emptyList();
events = Collections.emptyList();
}
// we already sent our ACK and now received the other one
else if (currentState == AWAIT_ACK) {
localState.put(TASK, TASK_ACTIVATE_CONTACT);
addAckData(localState, msg);
messages = Collections.emptyList();
events = Collections.emptyList();
}
// we are done (probably declined response), ignore & delete message
else if (currentState == FINISHED) {
return new StateUpdate<>(true, false, localState,
Collections.<BdfDictionary>emptyList(),
Collections.emptyList());
}
// this should not happen
else {
throw new IllegalArgumentException();
}
return new StateUpdate<>(false, false,
localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
}
private void addRequestData(BdfDictionary localState, BdfDictionary msg)
throws FormatException {
localState.put(NAME, msg.getString(NAME));
localState.put(PUBLIC_KEY, msg.getRaw(PUBLIC_KEY));
if (msg.containsKey(MSG)) {
localState.put(MSG, msg.getString(MSG));
}
}
private void addResponseData(BdfDictionary localState, BdfDictionary msg)
throws FormatException {
if (localState.containsKey(ACCEPT)) {
localState.put(ACCEPT,
localState.getBoolean(ACCEPT) && msg.getBoolean(ACCEPT));
} else {
localState.put(ACCEPT, msg.getBoolean(ACCEPT));
}
localState.put(NOT_OUR_RESPONSE, msg.getRaw(MESSAGE_ID));
if (msg.getBoolean(ACCEPT)) {
localState.put(TIME, msg.getLong(TIME));
localState.put(E_PUBLIC_KEY, msg.getRaw(E_PUBLIC_KEY));
localState.put(TRANSPORT, msg.getDictionary(TRANSPORT));
}
}
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 {
BdfDictionary m = new BdfDictionary();
m.put(TYPE, TYPE_ACK);
m.put(GROUP_ID, localState.getRaw(GROUP_ID));
m.put(SESSION_ID, localState.getRaw(SESSION_ID));
m.put(MAC, localState.getRaw(OUR_MAC));
m.put(SIGNATURE, localState.getRaw(OUR_SIGNATURE));
return m;
}
private void logAction(IntroduceeProtocolState state,
BdfDictionary localState, BdfDictionary msg) {
if (!LOG.isLoggable(INFO)) return;
try {
LOG.info("Sending " +
(localState.getBoolean(ACCEPT) ? "accept " : "decline ") +
"response in state " + state.name());
LOG.info("Moving on to state " +
getState(localState.getLong(STATE)).name());
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void logMessageReceived(IntroduceeProtocolState currentState,
IntroduceeProtocolState nextState, BdfDictionary localState,
int type, BdfDictionary msg) {
if (!LOG.isLoggable(INFO)) return;
String t = "unknown";
if (type == TYPE_REQUEST) t = "Introduction";
else if (type == TYPE_RESPONSE) t = "Response";
else if (type == TYPE_ACK) t = "ACK";
else if (type == TYPE_ABORT) t = "Abort";
LOG.info("Received " + t + " in state " + currentState.name());
LOG.info("Moving on to state " + nextState.name());
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
BdfDictionary localState, BdfDictionary delivered) {
try {
return noUpdate(localState);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
}
private IntroduceeProtocolState getState(Long state) {
return IntroduceeProtocolState.fromValue(state.intValue());
}
private Event getEvent(BdfDictionary localState, BdfDictionary msg)
throws FormatException {
ContactId contactId =
new ContactId(localState.getLong(CONTACT_ID_1).intValue());
AuthorId authorId = new AuthorId(localState.getRaw(REMOTE_AUTHOR_ID));
SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID));
MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID));
GroupId groupId = new GroupId(msg.getRaw(GROUP_ID));
long time = msg.getLong(MESSAGE_TIME);
String name = msg.getString(NAME);
String message = msg.getOptionalString(MSG);
boolean exists = localState.getBoolean(EXISTS);
boolean introducesOtherIdentity =
localState.getBoolean(REMOTE_AUTHOR_IS_US);
IntroductionRequest ir = new IntroductionRequest(sessionId, messageId,
groupId, ROLE_INTRODUCEE, time, false, false, false, false,
authorId, name, false, message, false, exists,
introducesOtherIdentity);
return new IntroductionRequestReceivedEvent(contactId, ir);
}
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
IntroduceeProtocolState currentState, BdfDictionary localState)
throws FormatException {
if (LOG.isLoggable(WARNING))
LOG.warning("Aborting protocol session in state " +
currentState.name());
localState.put(STATE, ERROR.getValue());
localState.put(TASK, TASK_ABORT);
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_ABORT);
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
List<BdfDictionary> messages = Collections.singletonList(msg);
// send abort event
ContactId contactId =
new ContactId(localState.getLong(CONTACT_ID_1).intValue());
SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID));
Event event = new IntroductionAbortedEvent(contactId, sessionId);
List<Event> events = Collections.singletonList(event);
return new StateUpdate<>(false, false, localState, messages, events);
}
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
BdfDictionary localState) throws FormatException {
return new StateUpdate<>(false, false, localState,
Collections.<BdfDictionary>emptyList(),
Collections.emptyList());
}
}

View File

@@ -1,569 +0,0 @@
package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent;
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;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ADDED_CONTACT_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_MAC_KEY_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_NONCE_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED;
import static org.briarproject.briar.api.introduction.IntroductionConstants.BOB_MAC_KEY_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.BOB_NONCE_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS;
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_PRIVATE_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.OUR_TRANSPORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SHARED_SECRET_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ACTIVATE_CONTACT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TASK_ADD_CONTACT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
@Immutable
@NotNullByDefault
class IntroduceeManager {
private static final Logger LOG =
Logger.getLogger(IntroduceeManager.class.getName());
private final MessageSender messageSender;
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final Clock clock;
private final CryptoComponent cryptoComponent;
private final TransportPropertyManager transportPropertyManager;
private final AuthorFactory authorFactory;
private final ContactManager contactManager;
private final IdentityManager identityManager;
private final IntroductionGroupFactory introductionGroupFactory;
@Inject
IntroduceeManager(MessageSender messageSender, DatabaseComponent db,
ClientHelper clientHelper, Clock clock,
CryptoComponent cryptoComponent,
TransportPropertyManager transportPropertyManager,
AuthorFactory authorFactory, ContactManager contactManager,
IdentityManager identityManager,
IntroductionGroupFactory introductionGroupFactory) {
this.messageSender = messageSender;
this.db = db;
this.clientHelper = clientHelper;
this.clock = clock;
this.cryptoComponent = cryptoComponent;
this.transportPropertyManager = transportPropertyManager;
this.authorFactory = authorFactory;
this.contactManager = contactManager;
this.identityManager = identityManager;
this.introductionGroupFactory = introductionGroupFactory;
}
public BdfDictionary initialize(Transaction txn, GroupId groupId,
BdfDictionary message) throws DbException, FormatException {
// create local message to keep engine state
long now = clock.currentTimeMillis();
Bytes salt = new Bytes(new byte[64]);
cryptoComponent.getSecureRandom().nextBytes(salt.getBytes());
Message localMsg = clientHelper.createMessage(
introductionGroupFactory.createLocalGroup().getId(), now,
BdfList.of(salt));
MessageId storageId = localMsg.getId();
// find out who is introducing us
BdfDictionary gd =
clientHelper.getGroupMetadataAsDictionary(txn, groupId);
ContactId introducerId =
new ContactId(gd.getLong(CONTACT).intValue());
Contact introducer = db.getContact(txn, introducerId);
BdfDictionary d = new BdfDictionary();
d.put(STORAGE_ID, storageId);
d.put(STATE, AWAIT_REQUEST.getValue());
d.put(ROLE, ROLE_INTRODUCEE);
d.put(GROUP_ID, groupId);
d.put(INTRODUCER, introducer.getAuthor().getName());
d.put(CONTACT_ID_1, introducer.getId().getInt());
d.put(LOCAL_AUTHOR_ID, introducer.getLocalAuthorId().getBytes());
d.put(NOT_OUR_RESPONSE, storageId);
d.put(ANSWERED, false);
// check if the contact we are introduced to does already exist
// TODO: Exchange author format version
AuthorId remoteAuthorId = authorFactory
.createAuthor(message.getString(NAME),
message.getRaw(PUBLIC_KEY)).getId();
boolean exists = contactManager.contactExists(txn, remoteAuthorId,
introducer.getLocalAuthorId());
d.put(EXISTS, exists);
d.put(REMOTE_AUTHOR_ID, remoteAuthorId);
// check if someone is trying to introduce us to ourselves
if (remoteAuthorId.equals(introducer.getLocalAuthorId())) {
LOG.warning("Received Introduction Request to Ourselves");
throw new FormatException();
}
// check if remote author is actually one of our other identities
boolean introducesOtherIdentity =
db.containsLocalAuthor(txn, remoteAuthorId);
d.put(REMOTE_AUTHOR_IS_US, introducesOtherIdentity);
// save local state to database
clientHelper.addLocalMessage(txn, localMsg, d, false);
return d;
}
public void incomingMessage(Transaction txn, BdfDictionary state,
BdfDictionary message) throws DbException, FormatException {
IntroduceeEngine engine = new IntroduceeEngine();
processStateUpdate(txn, message,
engine.onMessageReceived(state, message));
}
void acceptIntroduction(Transaction txn, BdfDictionary state,
long timestamp) throws DbException, FormatException {
// get data to connect and derive a shared secret later
long now = clock.currentTimeMillis();
KeyPair keyPair = cryptoComponent.generateAgreementKeyPair();
byte[] publicKey = keyPair.getPublic().getEncoded();
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, tp);
localAction.put(MESSAGE_TIME, timestamp);
// start engine and process its state update
IntroduceeEngine engine = new IntroduceeEngine();
processStateUpdate(txn, null, engine.onLocalAction(state, localAction));
}
void declineIntroduction(Transaction txn, BdfDictionary state,
long timestamp) throws DbException, FormatException {
// update session state
state.put(ACCEPT, false);
// define action
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, TYPE_RESPONSE);
localAction.put(MESSAGE_TIME, timestamp);
// start engine and process its state update
IntroduceeEngine engine = new IntroduceeEngine();
processStateUpdate(txn, null,
engine.onLocalAction(state, localAction));
}
private void processStateUpdate(Transaction txn,
@Nullable BdfDictionary msg,
IntroduceeEngine.StateUpdate<BdfDictionary, BdfDictionary> result)
throws DbException, FormatException {
// perform actions based on new local state
BdfDictionary followUpAction = performTasks(txn, result.localState);
// save new local state
MessageId storageId =
new MessageId(result.localState.getRaw(STORAGE_ID));
clientHelper.mergeMessageMetadata(txn, storageId, result.localState);
// send messages
for (BdfDictionary d : result.toSend) {
messageSender.sendMessage(txn, d);
}
// broadcast events
for (Event event : result.toBroadcast) {
txn.attach(event);
}
// delete message
if (result.deleteMessage && msg != null) {
MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID));
if (LOG.isLoggable(INFO)) {
LOG.info("Deleting message with id " + messageId.hashCode());
}
db.deleteMessage(txn, messageId);
db.deleteMessageMetadata(txn, messageId);
}
// process follow up action at the end if available
if (followUpAction != null) {
IntroduceeEngine engine = new IntroduceeEngine();
processStateUpdate(txn, null,
engine.onLocalAction(result.localState, followUpAction));
}
}
@Nullable
private BdfDictionary performTasks(Transaction txn,
BdfDictionary localState) throws FormatException, DbException {
if (!localState.containsKey(TASK) || localState.get(TASK) == NULL_VALUE)
return null;
// remember task and remove it from localState
long task = localState.getLong(TASK);
localState.put(TASK, NULL_VALUE);
if (task == TASK_ADD_CONTACT) {
if (localState.getBoolean(EXISTS)) {
// we have this contact already, so do not perform actions
LOG.info("We have this contact already, do not add");
return null;
}
// figure out who takes which role by comparing public keys
byte[] ourPublicKeyBytes = localState.getRaw(OUR_PUBLIC_KEY);
byte[] theirPublicKeyBytes = localState.getRaw(E_PUBLIC_KEY);
int comp = Bytes.COMPARATOR.compare(new Bytes(ourPublicKeyBytes),
new Bytes(theirPublicKeyBytes));
boolean alice = comp < 0;
// get our local author
LocalAuthor author = identityManager.getLocalAuthor(txn);
SecretKey secretKey;
byte[] ourPrivateKeyBytes = localState.getRaw(OUR_PRIVATE_KEY);
try {
// derive secret master key
secretKey = deriveSecretKey(ourPublicKeyBytes,
ourPrivateKeyBytes, alice, theirPublicKeyBytes);
// derive MAC keys and nonces, sign our nonce and calculate MAC
deriveMacKeysAndNonces(localState, author, secretKey, alice);
} catch (GeneralSecurityException e) {
// we can not continue without the signature
throw new DbException(e);
}
LOG.info("Adding contact in inactive state");
// 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 as inactive
// TODO: Exchange author format version
Author remoteAuthor = authorFactory
.createAuthor(localState.getString(NAME),
localState.getRaw(PUBLIC_KEY));
ContactId contactId = contactManager
.addContact(txn, remoteAuthor, author.getId(), secretKey,
timestamp, alice, false, false);
// Update local state with ContactId, so we know what to activate
localState.put(ADDED_CONTACT_ID, contactId.getInt());
// let the transport manager know how to connect to the contact
Map<TransportId, TransportProperties> transportProperties =
parseTransportProperties(localState);
transportPropertyManager.addRemoteProperties(txn, contactId,
transportProperties);
// delete the ephemeral private key by overwriting with NULL value
// this ensures future ephemeral keys can not be recovered when
// this device should gets compromised
localState.put(OUR_PRIVATE_KEY, NULL_VALUE);
// define next action: Send ACK
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, TYPE_ACK);
// return follow up action to start engine
// and process its state update again
return localAction;
}
// we sent and received an ACK, so activate contact
if (task == TASK_ACTIVATE_CONTACT) {
if (!localState.getBoolean(EXISTS) &&
localState.containsKey(ADDED_CONTACT_ID)) {
try {
LOG.info("Verifying Signature...");
verifySignature(localState);
LOG.info("Verifying MAC...");
verifyMac(localState);
} catch (GeneralSecurityException e) {
throw new DbException(e);
}
LOG.info("Activating Contact...");
ContactId contactId = new ContactId(
localState.getLong(ADDED_CONTACT_ID).intValue());
// activate and show contact in contact list
contactManager.setContactActive(txn, contactId, true);
// broadcast event informing of successful introduction
Contact contact = db.getContact(txn, contactId);
Event event = new IntroductionSucceededEvent(contact);
txn.attach(event);
} else {
LOG.info(
"We must have had this contact already, not activating...");
}
}
// we need to abort the protocol, clean up what has been done
if (task == TASK_ABORT) {
if (localState.containsKey(ADDED_CONTACT_ID)) {
LOG.info("Deleting added contact due to abort...");
ContactId contactId = new ContactId(
localState.getLong(ADDED_CONTACT_ID).intValue());
contactManager.removeContact(txn, contactId);
}
}
return null;
}
private SecretKey deriveSecretKey(byte[] ourPublicKeyBytes,
byte[] ourPrivateKeyBytes, boolean alice,
byte[] theirPublicKeyBytes) throws GeneralSecurityException {
// parse the local ephemeral key pair
KeyParser keyParser = cryptoComponent.getAgreementKeyParser();
PublicKey ourPublicKey;
PrivateKey ourPrivateKey;
try {
ourPublicKey = keyParser.parsePublicKey(ourPublicKeyBytes);
ourPrivateKey = keyParser.parsePrivateKey(ourPrivateKeyBytes);
} catch (GeneralSecurityException e) {
if (LOG.isLoggable(WARNING)) {
LOG.log(WARNING, e.toString(), e);
}
throw new RuntimeException("Our own ephemeral key is invalid");
}
KeyPair ourKeyPair = new KeyPair(ourPublicKey, ourPrivateKey);
PublicKey theirPublicKey =
keyParser.parsePublicKey(theirPublicKeyBytes);
// The shared secret is derived from the local ephemeral key pair
// and the remote ephemeral public key
byte[][] inputs = {
new byte[] {CLIENT_VERSION},
alice ? ourPublicKeyBytes : theirPublicKeyBytes,
alice ? theirPublicKeyBytes : ourPublicKeyBytes
};
return cryptoComponent.deriveSharedSecret(SHARED_SECRET_LABEL,
theirPublicKey, ourKeyPair, inputs);
}
/**
* Derives nonces, signs our nonce and calculates MAC
* <p>
* Derives two nonces and two MAC keys from the shared secret key.
* The other introducee's nonce and MAC key are added to the localState.
* <p>
* Our nonce is signed with the local author's long-term private key.
* The signature is added to the localState.
* <p>
* Calculates a MAC and stores it in the localState.
*/
private void deriveMacKeysAndNonces(BdfDictionary localState,
LocalAuthor author, SecretKey secretKey, boolean alice)
throws FormatException, GeneralSecurityException {
// Derive two nonces and two MAC keys from the shared secret key
String ourNonceLabel = alice ? ALICE_NONCE_LABEL : BOB_NONCE_LABEL;
String theirNonceLabel = alice ? BOB_NONCE_LABEL : ALICE_NONCE_LABEL;
byte[] ourNonce = cryptoComponent.mac(ourNonceLabel, secretKey);
byte[] theirNonce = cryptoComponent.mac(theirNonceLabel, secretKey);
String ourKeyLabel = alice ? ALICE_MAC_KEY_LABEL : BOB_MAC_KEY_LABEL;
String theirKeyLabel = alice ? BOB_MAC_KEY_LABEL : ALICE_MAC_KEY_LABEL;
SecretKey ourMacKey = cryptoComponent.deriveKey(ourKeyLabel, secretKey);
SecretKey theirMacKey =
cryptoComponent.deriveKey(theirKeyLabel, secretKey);
// 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
byte[] sig = cryptoComponent.sign(SIGNING_LABEL, ourNonce,
author.getPrivateKey());
// Calculate a MAC over identity public key, ephemeral public key,
// transport properties and timestamp.
byte[] publicKeyBytes = localState.getRaw(OUR_PUBLIC_KEY);
BdfDictionary tp = localState.getDictionary(OUR_TRANSPORT);
long ourTime = localState.getLong(OUR_TIME);
BdfList toMacList = BdfList.of(author.getPublicKey(),
publicKeyBytes, tp, ourTime);
byte[] toMac = clientHelper.toByteArray(toMacList);
byte[] mac = cryptoComponent.mac(MAC_LABEL, ourMacKey, toMac);
// Add MAC and signature to localState, so it can be included in ACK
localState.put(OUR_MAC, mac);
localState.put(OUR_SIGNATURE, sig);
}
void verifySignature(BdfDictionary localState)
throws FormatException, GeneralSecurityException {
byte[] nonce = localState.getRaw(NONCE);
byte[] sig = localState.getRaw(SIGNATURE);
byte[] key = localState.getRaw(PUBLIC_KEY);
// Verify the signature
if (!cryptoComponent.verifySignature(sig, SIGNING_LABEL, nonce, key)) {
LOG.warning("Invalid nonce signature in ACK");
throw new GeneralSecurityException();
}
}
void verifyMac(BdfDictionary localState)
throws FormatException, GeneralSecurityException {
// 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 toMacList = BdfList.of(pubKey, ePubKey, tp, timestamp);
byte[] toMac = clientHelper.toByteArray(toMacList);
byte[] calculatedMac = cryptoComponent.mac(MAC_LABEL, macKey, toMac);
if (!Arrays.equals(mac, calculatedMac)) {
LOG.warning("Received ACK with invalid MAC");
throw new GeneralSecurityException();
}
}
public void abort(Transaction txn, BdfDictionary state) {
IntroduceeEngine engine = new IntroduceeEngine();
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, TYPE_ABORT);
try {
processStateUpdate(txn, null,
engine.onLocalAction(state, localAction));
} catch (DbException | IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private BdfDictionary encodeTransportProperties(
Map<TransportId, TransportProperties> map) {
BdfDictionary d = new BdfDictionary();
for (Map.Entry<TransportId, TransportProperties> e : map.entrySet()) {
d.put(e.getKey().getString(), e.getValue());
}
return d;
}
private Map<TransportId, TransportProperties> parseTransportProperties(
BdfDictionary d) throws FormatException {
Map<TransportId, TransportProperties> tpMap = new HashMap<>();
BdfDictionary tpMapDict = d.getDictionary(TRANSPORT);
for (String key : tpMapDict.keySet()) {
TransportId transportId = new TransportId(key);
TransportProperties transportProperties = new TransportProperties();
BdfDictionary tpDict = tpMapDict.getDictionary(key);
for (String tkey : tpDict.keySet()) {
transportProperties.put(tkey, tpDict.getString(tkey));
}
tpMap.put(transportId, transportProperties);
}
return tpMap;
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
@@ -28,11 +28,12 @@ import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction2.IntroductionResponse; import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.introduction2.event.IntroductionRequestReceivedEvent; import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.api.introduction2.event.IntroductionResponseReceivedEvent; import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
import org.briarproject.briar.api.introduction2.event.IntroductionSucceededEvent; import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Map; import java.util.Map;
@@ -41,11 +42,11 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_AUTH; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH;
import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_RESPONSES; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES;
import static org.briarproject.briar.introduction2.IntroduceeState.LOCAL_ACCEPTED; import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED;
import static org.briarproject.briar.introduction2.IntroduceeState.REMOTE_ACCEPTED; import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_ACCEPTED;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -145,10 +146,11 @@ class IntroduceeProtocolEngine
IntroduceeSession session, AcceptMessage m) IntroduceeSession session, AcceptMessage m)
throws DbException, FormatException { throws DbException, FormatException {
switch (session.getState()) { switch (session.getState()) {
case START:
return onRemoteResponseInStart(txn, session, m);
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
return onRemoteAccept(txn, session, m); return onRemoteAccept(txn, session, m);
case START:
case LOCAL_DECLINED: case LOCAL_DECLINED:
case REMOTE_ACCEPTED: case REMOTE_ACCEPTED:
case AWAIT_AUTH: case AWAIT_AUTH:
@@ -165,7 +167,7 @@ class IntroduceeProtocolEngine
throws DbException, FormatException { throws DbException, FormatException {
switch (session.getState()) { switch (session.getState()) {
case START: case START:
return session; // Ignore in the START state return onRemoteResponseInStart(txn, session, m);
case AWAIT_RESPONSES: case AWAIT_RESPONSES:
case LOCAL_DECLINED: case LOCAL_DECLINED:
case LOCAL_ACCEPTED: case LOCAL_ACCEPTED:
@@ -322,18 +324,6 @@ class IntroduceeProtocolEngine
if (isInvalidDependency(s, m.getPreviousMessageId())) if (isInvalidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s);
// Broadcast IntroductionResponseReceivedEvent
Contact c = contactManager.getContact(s.getIntroducer().getId(),
identityManager.getLocalAuthor(txn).getId());
IntroductionResponse request =
new IntroductionResponse(s.getSessionId(), m.getMessageId(),
m.getGroupId(), INTRODUCEE, m.getTimestamp(), false,
false, false, false, s.getRemoteAuthor().getName(),
true);
IntroductionResponseReceivedEvent e =
new IntroductionResponseReceivedEvent(c.getId(), request);
txn.attach(e);
// Determine next state // Determine next state
IntroduceeState state = IntroduceeState state =
s.getState() == AWAIT_RESPONSES ? REMOTE_ACCEPTED : AWAIT_AUTH; s.getState() == AWAIT_RESPONSES ? REMOTE_ACCEPTED : AWAIT_AUTH;
@@ -356,12 +346,46 @@ class IntroduceeProtocolEngine
if (isInvalidDependency(s, m.getPreviousMessageId())) if (isInvalidDependency(s, m.getPreviousMessageId()))
return abort(txn, s); return abort(txn, s);
// Mark the request visible in the UI
markMessageVisibleInUi(txn, m.getMessageId());
// Track the incoming message
messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
// Broadcast IntroductionResponseReceivedEvent
Contact c = contactManager.getContact(txn, s.getIntroducer().getId(),
identityManager.getLocalAuthor(txn).getId());
IntroductionResponse request =
new IntroductionResponse(s.getSessionId(), m.getMessageId(),
m.getGroupId(), INTRODUCEE, m.getTimestamp(), false,
false, false, false, s.getRemoteAuthor().getName(),
false);
IntroductionResponseReceivedEvent e =
new IntroductionResponseReceivedEvent(c.getId(), request);
txn.attach(e);
// Move back to START state // Move back to START state
return IntroduceeSession return IntroduceeSession
.clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(),
m.getMessageId()); m.getMessageId());
} }
private IntroduceeSession onRemoteResponseInStart(Transaction txn,
IntroduceeSession s, AbstractIntroductionMessage m) throws DbException {
// The timestamp must be higher than the last request message
if (m.getTimestamp() <= s.getRequestTimestamp())
return abort(txn, s);
// The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getPreviousMessageId()))
return abort(txn, s);
// Stay in START state
return IntroduceeSession
.clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(),
m.getMessageId());
}
private IntroduceeSession onLocalAuth(Transaction txn, IntroduceeSession s) private IntroduceeSession onLocalAuth(Transaction txn, IntroduceeSession s)
throws DbException { throws DbException {
boolean alice = isAlice(txn, s); boolean alice = isAlice(txn, s);
@@ -459,6 +483,9 @@ class IntroduceeProtocolEngine
if (requestId == null) throw new IllegalStateException(); if (requestId == null) throw new IllegalStateException();
markRequestUnavailableToAnswer(txn, requestId); markRequestUnavailableToAnswer(txn, requestId);
// Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Reset the session back to initial state // Reset the session back to initial state
return IntroduceeSession return IntroduceeSession
.clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(), .clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(),
@@ -475,6 +502,9 @@ class IntroduceeProtocolEngine
// Send an ABORT message // Send an ABORT message
Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s)); Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s));
// Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Reset the session back to initial state // Reset the session back to initial state
return IntroduceeSession.clear(s, sent.getId(), sent.getTimestamp(), return IntroduceeSession.clear(s, sent.getId(), sent.getTimestamp(),
s.getLastRemoteMessageId()); s.getLastRemoteMessageId());

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
@@ -10,16 +10,16 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.Role; import org.briarproject.briar.api.introduction.Role;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_ACTIVATE; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_ACTIVATE;
import static org.briarproject.briar.introduction2.IntroduceeState.START; import static org.briarproject.briar.introduction.IntroduceeState.START;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;

View File

@@ -1,370 +0,0 @@
package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.ProtocolEngine;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction.IntroducerAction;
import org.briarproject.briar.api.introduction.IntroducerProtocolState;
import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.briar.api.introduction.IntroducerAction.LOCAL_ABORT;
import static org.briarproject.briar.api.introduction.IntroducerAction.LOCAL_REQUEST;
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_1;
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_ACCEPT_2;
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_1;
import static org.briarproject.briar.api.introduction.IntroducerAction.REMOTE_DECLINE_2;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_ACKS;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_ACK_1;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_ACK_2;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSE_1;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSE_2;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.ERROR;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.FINISHED;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
@Immutable
@NotNullByDefault
class IntroducerEngine
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
private static final Logger LOG =
Logger.getLogger(IntroducerEngine.class.getName());
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
BdfDictionary localState, BdfDictionary localAction) {
try {
IntroducerProtocolState currentState =
getState(localState.getLong(STATE));
int type = localAction.getLong(TYPE).intValue();
IntroducerAction action = IntroducerAction.getLocal(type);
IntroducerProtocolState nextState = currentState.next(action);
if (action == LOCAL_ABORT && currentState != ERROR) {
return abortSession(currentState, localState);
}
if (nextState == ERROR) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Error: Invalid action in state " +
currentState.name());
}
return noUpdate(localState);
}
localState.put(STATE, nextState.getValue());
if (action == LOCAL_REQUEST) {
// create the introduction requests for both contacts
List<BdfDictionary> messages = new ArrayList<>(2);
BdfDictionary msg1 = new BdfDictionary();
msg1.put(TYPE, TYPE_REQUEST);
msg1.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg1.put(GROUP_ID, localState.getRaw(GROUP_ID_1));
msg1.put(NAME, localState.getString(CONTACT_2));
msg1.put(PUBLIC_KEY, localAction.getRaw(PUBLIC_KEY2));
if (localAction.containsKey(MSG)) {
msg1.put(MSG, localAction.getString(MSG));
}
msg1.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME));
messages.add(msg1);
logLocalAction(currentState, localState);
BdfDictionary msg2 = new BdfDictionary();
msg2.put(TYPE, TYPE_REQUEST);
msg2.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg2.put(GROUP_ID, localState.getRaw(GROUP_ID_2));
msg2.put(NAME, localState.getString(CONTACT_1));
msg2.put(PUBLIC_KEY, localAction.getRaw(PUBLIC_KEY1));
if (localAction.containsKey(MSG)) {
msg2.put(MSG, localAction.getString(MSG));
}
msg2.put(MESSAGE_TIME, localAction.getLong(MESSAGE_TIME));
messages.add(msg2);
logLocalAction(currentState, localState);
List<Event> events = Collections.emptyList();
return new StateUpdate<>(false, false,
localState, messages, events);
} else {
throw new IllegalArgumentException("Unknown Local Action");
}
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
BdfDictionary localState, BdfDictionary msg) {
try {
IntroducerProtocolState currentState =
getState(localState.getLong(STATE));
int type = msg.getLong(TYPE).intValue();
boolean one = isContact1(localState, msg);
IntroducerAction action = IntroducerAction.getRemote(type, one);
IntroducerProtocolState nextState = currentState.next(action);
logMessageReceived(currentState, nextState, localState, type, msg);
if (nextState == ERROR) {
if (currentState != ERROR) {
return abortSession(currentState, localState);
} else {
return noUpdate(localState);
}
}
List<BdfDictionary> messages;
List<Event> events;
// we have sent our requests and just got the 1st or 2nd response
if (currentState == AWAIT_RESPONSES ||
currentState == AWAIT_RESPONSE_1 ||
currentState == AWAIT_RESPONSE_2) {
// update next state based on message content
action = IntroducerAction
.getRemote(type, one, msg.getBoolean(ACCEPT));
nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
if (one) localState.put(RESPONSE_1, msg.getRaw(MESSAGE_ID));
else localState.put(RESPONSE_2, msg.getRaw(MESSAGE_ID));
messages = forwardMessage(localState, msg);
events = Collections.singletonList(getEvent(localState, msg));
}
// we have forwarded both responses and now received the 1st or 2nd ACK
else if (currentState == AWAIT_ACKS ||
currentState == AWAIT_ACK_1 ||
currentState == AWAIT_ACK_2) {
localState.put(STATE, nextState.getValue());
messages = forwardMessage(localState, msg);
events = Collections.emptyList();
}
// we probably received a response while already being FINISHED
else if (currentState == FINISHED) {
// if it was a response store it to be found later
if (action == REMOTE_ACCEPT_1 || action == REMOTE_DECLINE_1) {
localState.put(RESPONSE_1, msg.getRaw(MESSAGE_ID));
messages = Collections.emptyList();
events = Collections
.singletonList(getEvent(localState, msg));
} else if (action == REMOTE_ACCEPT_2 ||
action == REMOTE_DECLINE_2) {
localState.put(RESPONSE_2, msg.getRaw(MESSAGE_ID));
messages = Collections.emptyList();
events = Collections
.singletonList(getEvent(localState, msg));
} else return noUpdate(localState);
} else {
throw new IllegalArgumentException("Bad state");
}
return new StateUpdate<>(false, false,
localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
}
private void logLocalAction(IntroducerProtocolState state,
BdfDictionary localState) {
if (!LOG.isLoggable(INFO)) return;
try {
LOG.info("Sending introduction request in state " + state.name());
LOG.info("Moving on to state " +
getState(localState.getLong(STATE)).name());
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void logMessageReceived(IntroducerProtocolState currentState,
IntroducerProtocolState nextState,
BdfDictionary localState, int type, BdfDictionary msg) {
if (!LOG.isLoggable(INFO)) return;
String t = "unknown";
if (type == TYPE_REQUEST) t = "Introduction";
else if (type == TYPE_RESPONSE) t = "Response";
else if (type == TYPE_ACK) t = "ACK";
else if (type == TYPE_ABORT) t = "Abort";
LOG.info("Received " + t + " in state " + currentState.name());
LOG.info("Moving on to state " + nextState.name());
}
private List<BdfDictionary> forwardMessage(BdfDictionary localState,
BdfDictionary message) throws FormatException {
// clone the message here, because we still need the original
BdfDictionary msg = (BdfDictionary) message.clone();
if (isContact1(localState, msg)) {
msg.put(GROUP_ID, localState.getRaw(GROUP_ID_2));
} else {
msg.put(GROUP_ID, localState.getRaw(GROUP_ID_1));
}
return Collections.singletonList(msg);
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
BdfDictionary localState, BdfDictionary delivered) {
try {
return noUpdate(localState);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
}
private IntroducerProtocolState getState(Long state) {
return IntroducerProtocolState.fromValue(state.intValue());
}
private Event getEvent(BdfDictionary localState, BdfDictionary msg)
throws FormatException {
ContactId contactId =
new ContactId(localState.getLong(CONTACT_ID_1).intValue());
AuthorId authorId = new AuthorId(localState.getRaw(AUTHOR_ID_1));
if (Arrays
.equals(msg.getRaw(GROUP_ID), localState.getRaw(GROUP_ID_2))) {
contactId =
new ContactId(localState.getLong(CONTACT_ID_2).intValue());
authorId = new AuthorId(localState.getRaw(AUTHOR_ID_2));
}
SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID));
MessageId messageId = new MessageId(msg.getRaw(MESSAGE_ID));
GroupId groupId = new GroupId(msg.getRaw(GROUP_ID));
long time = msg.getLong(MESSAGE_TIME);
String name = getOtherContact(localState, msg);
boolean accept = msg.getBoolean(ACCEPT);
IntroductionResponse ir =
new IntroductionResponse(sessionId, messageId, groupId,
ROLE_INTRODUCER, time, false, false, false, false,
authorId, name, accept);
return new IntroductionResponseReceivedEvent(contactId, ir);
}
private boolean isContact1(BdfDictionary localState, BdfDictionary msg)
throws FormatException {
byte[] group = msg.getRaw(GROUP_ID);
byte[] group1 = localState.getRaw(GROUP_ID_1);
byte[] group2 = localState.getRaw(GROUP_ID_2);
if (Arrays.equals(group, group1)) {
return true;
} else if (Arrays.equals(group, group2)) {
return false;
} else {
throw new FormatException();
}
}
private String getOtherContact(BdfDictionary localState, BdfDictionary msg)
throws FormatException {
String to = localState.getString(CONTACT_2);
if (Arrays
.equals(msg.getRaw(GROUP_ID), localState.getRaw(GROUP_ID_2))) {
to = localState.getString(CONTACT_1);
}
return to;
}
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
IntroducerProtocolState currentState, BdfDictionary localState)
throws FormatException {
if (LOG.isLoggable(WARNING))
LOG.warning("Aborting protocol session in state " +
currentState.name());
localState.put(STATE, ERROR.getValue());
List<BdfDictionary> messages = new ArrayList<>(2);
BdfDictionary msg1 = new BdfDictionary();
msg1.put(TYPE, TYPE_ABORT);
msg1.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg1.put(GROUP_ID, localState.getRaw(GROUP_ID_1));
messages.add(msg1);
BdfDictionary msg2 = new BdfDictionary();
msg2.put(TYPE, TYPE_ABORT);
msg2.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg2.put(GROUP_ID, localState.getRaw(GROUP_ID_2));
messages.add(msg2);
// send one abort event per contact
List<Event> events = new ArrayList<>(2);
SessionId sessionId = new SessionId(localState.getRaw(SESSION_ID));
ContactId contactId1 =
new ContactId(localState.getLong(CONTACT_ID_1).intValue());
ContactId contactId2 =
new ContactId(localState.getLong(CONTACT_ID_2).intValue());
Event event1 = new IntroductionAbortedEvent(contactId1, sessionId);
events.add(event1);
Event event2 = new IntroductionAbortedEvent(contactId2, sessionId);
events.add(event2);
return new StateUpdate<>(false, false, localState, messages, events);
}
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
BdfDictionary localState) throws FormatException {
return new StateUpdate<>(false, false, localState,
Collections.<BdfDictionary>emptyList(),
Collections.emptyList());
}
}

View File

@@ -1,181 +0,0 @@
package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils;
import java.io.IOException;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS;
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
@Immutable
@NotNullByDefault
class IntroducerManager {
private static final Logger LOG =
Logger.getLogger(IntroducerManager.class.getName());
private final MessageSender messageSender;
private final ClientHelper clientHelper;
private final Clock clock;
private final CryptoComponent cryptoComponent;
private final IntroductionGroupFactory introductionGroupFactory;
@Inject
IntroducerManager(MessageSender messageSender, ClientHelper clientHelper,
Clock clock, CryptoComponent cryptoComponent,
IntroductionGroupFactory introductionGroupFactory) {
this.messageSender = messageSender;
this.clientHelper = clientHelper;
this.clock = clock;
this.cryptoComponent = cryptoComponent;
this.introductionGroupFactory = introductionGroupFactory;
}
public BdfDictionary initialize(Transaction txn, Contact c1, Contact c2)
throws FormatException, DbException {
// create local message to keep engine state
long now = clock.currentTimeMillis();
Bytes salt = new Bytes(new byte[64]);
cryptoComponent.getSecureRandom().nextBytes(salt.getBytes());
Message m = clientHelper.createMessage(
introductionGroupFactory.createLocalGroup().getId(), now,
BdfList.of(salt));
MessageId sessionId = m.getId();
Group g1 = introductionGroupFactory.createIntroductionGroup(c1);
Group g2 = introductionGroupFactory.createIntroductionGroup(c2);
BdfDictionary d = new BdfDictionary();
d.put(SESSION_ID, sessionId);
d.put(STORAGE_ID, sessionId);
d.put(STATE, PREPARE_REQUESTS.getValue());
d.put(ROLE, ROLE_INTRODUCER);
d.put(GROUP_ID_1, g1.getId());
d.put(GROUP_ID_2, g2.getId());
d.put(CONTACT_1, c1.getAuthor().getName());
d.put(CONTACT_2, c2.getAuthor().getName());
d.put(CONTACT_ID_1, c1.getId().getInt());
d.put(CONTACT_ID_2, c2.getId().getInt());
d.put(AUTHOR_ID_1, c1.getAuthor().getId());
d.put(AUTHOR_ID_2, c2.getAuthor().getId());
// save local state to database
clientHelper.addLocalMessage(txn, m, d, false);
return d;
}
void makeIntroduction(Transaction txn, Contact c1, Contact c2,
@Nullable String msg, long timestamp)
throws DbException, FormatException {
// TODO check for existing session with those contacts?
// deny new introduction under which conditions?
// initialize engine state
BdfDictionary localState = initialize(txn, c1, c2);
// define action
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, TYPE_REQUEST);
if (!StringUtils.isNullOrEmpty(msg)) {
int msgLength = StringUtils.toUtf8(msg).length;
if (msgLength > MAX_INTRODUCTION_MESSAGE_LENGTH)
throw new IllegalArgumentException();
localAction.put(MSG, msg);
}
localAction.put(PUBLIC_KEY1, c1.getAuthor().getPublicKey());
localAction.put(PUBLIC_KEY2, c2.getAuthor().getPublicKey());
localAction.put(MESSAGE_TIME, timestamp);
// start engine and process its state update
IntroducerEngine engine = new IntroducerEngine();
processStateUpdate(txn,
engine.onLocalAction(localState, localAction));
}
public void incomingMessage(Transaction txn, BdfDictionary state,
BdfDictionary message) throws DbException, FormatException {
IntroducerEngine engine = new IntroducerEngine();
processStateUpdate(txn,
engine.onMessageReceived(state, message));
}
private void processStateUpdate(Transaction txn,
IntroducerEngine.StateUpdate<BdfDictionary, BdfDictionary>
result) throws DbException, FormatException {
// save new local state
MessageId storageId =
new MessageId(result.localState.getRaw(STORAGE_ID));
clientHelper.mergeMessageMetadata(txn, storageId, result.localState);
// send messages
for (BdfDictionary d : result.toSend) {
messageSender.sendMessage(txn, d);
}
// broadcast events
for (Event event : result.toBroadcast) {
txn.attach(event);
}
}
public void abort(Transaction txn, BdfDictionary state) {
IntroducerEngine engine = new IntroducerEngine();
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, TYPE_ABORT);
try {
processStateUpdate(txn,
engine.onLocalAction(state, localAction));
} catch (DbException | IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
@@ -17,9 +18,10 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.introduction2.IntroductionResponse; import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.introduction2.event.IntroductionResponseReceivedEvent; import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.introduction2.IntroducerSession.Introducee; import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
import java.util.Map; import java.util.Map;
@@ -27,17 +29,17 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATES; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATE_A; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATE_B; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTHS; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTHS;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTH_A; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTH_A;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTH_B; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTH_B;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSES; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSES;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSE_A; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_A;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSE_B; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_B;
import static org.briarproject.briar.introduction2.IntroducerState.START; import static org.briarproject.briar.introduction.IntroducerState.START;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -94,6 +96,11 @@ class IntroducerProtocolEngine
throw new UnsupportedOperationException(); // Invalid in this role throw new UnsupportedOperationException(); // Invalid in this role
} }
IntroducerSession onAbortAction(Transaction txn, IntroducerSession s)
throws DbException, FormatException {
return abort(txn, s);
}
@Override @Override
public IntroducerSession onRequestMessage(Transaction txn, public IntroducerSession onRequestMessage(Transaction txn,
IntroducerSession s, RequestMessage m) IntroducerSession s, RequestMessage m)
@@ -111,8 +118,7 @@ class IntroducerProtocolEngine
case AWAIT_RESPONSE_B: case AWAIT_RESPONSE_B:
return onRemoteAccept(txn, s, m); return onRemoteAccept(txn, s, m);
case START: case START:
// TODO check and update lastRemoteMsgId? return onRemoteResponseInStart(txn, s, m);
return s; // Ignored in this state
case AWAIT_AUTHS: case AWAIT_AUTHS:
case AWAIT_AUTH_A: case AWAIT_AUTH_A:
case AWAIT_AUTH_B: case AWAIT_AUTH_B:
@@ -135,8 +141,7 @@ class IntroducerProtocolEngine
case AWAIT_RESPONSE_B: case AWAIT_RESPONSE_B:
return onRemoteDecline(txn, s, m); return onRemoteDecline(txn, s, m);
case START: case START:
// TODO check and update lastRemoteMsgId? return onRemoteResponseInStart(txn, s, m);
return s; // Ignored in this state
case AWAIT_AUTHS: case AWAIT_AUTHS:
case AWAIT_AUTH_A: case AWAIT_AUTH_A:
case AWAIT_AUTH_B: case AWAIT_AUTH_B:
@@ -253,14 +258,16 @@ class IntroducerProtocolEngine
if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_A; if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_A;
introducee1 = new Introducee(s.getIntroducee1(), sent); introducee1 = new Introducee(s.getIntroducee1(), sent);
introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId());
c = contactManager.getContact(s.getIntroducee2().author.getId(), c = contactManager
identityManager.getLocalAuthor(txn).getId()); .getContact(txn, s.getIntroducee2().author.getId(),
identityManager.getLocalAuthor(txn).getId());
} else if (i.equals(s.getIntroducee2())) { } else if (i.equals(s.getIntroducee2())) {
if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_B; if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_B;
introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId());
introducee2 = new Introducee(s.getIntroducee2(), sent); introducee2 = new Introducee(s.getIntroducee2(), sent);
c = contactManager.getContact(s.getIntroducee1().author.getId(), c = contactManager
identityManager.getLocalAuthor(txn).getId()); .getContact(txn, s.getIntroducee1().author.getId(),
identityManager.getLocalAuthor(txn).getId());
} else throw new AssertionError(); } else throw new AssertionError();
// Broadcast IntroductionResponseReceivedEvent // Broadcast IntroductionResponseReceivedEvent
@@ -296,22 +303,23 @@ class IntroducerProtocolEngine
Introducee i = getOtherIntroducee(s, m.getGroupId()); Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i); long timestamp = getLocalTimestamp(s, i);
Message sent = sendDeclineMessage(txn, i, timestamp, false); Message sent = sendDeclineMessage(txn, i, timestamp, false);
// Track the message
messageTracker.trackOutgoingMessage(txn, sent);
// Move to the START state // Move to the START state
Introducee introducee1, introducee2; Introducee introducee1, introducee2;
AuthorId localAuthorId =identityManager.getLocalAuthor(txn).getId();
Contact c; Contact c;
if (i.equals(s.getIntroducee1())) { if (i.equals(s.getIntroducee1())) {
introducee1 = new Introducee(s.getIntroducee1(), sent); introducee1 = new Introducee(s.getIntroducee1(), sent);
introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId()); introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId());
c = contactManager.getContact(s.getIntroducee2().author.getId(), c = contactManager
identityManager.getLocalAuthor(txn).getId()); .getContact(txn, s.getIntroducee2().author.getId(),
localAuthorId);
} else if (i.equals(s.getIntroducee2())) { } else if (i.equals(s.getIntroducee2())) {
introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId()); introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId());
introducee2 = new Introducee(s.getIntroducee2(), sent); introducee2 = new Introducee(s.getIntroducee2(), sent);
c = contactManager.getContact(s.getIntroducee2().author.getId(), c = contactManager
identityManager.getLocalAuthor(txn).getId()); .getContact(txn, s.getIntroducee1().author.getId(),
localAuthorId);
} else throw new AssertionError(); } else throw new AssertionError();
// Broadcast IntroductionResponseReceivedEvent // Broadcast IntroductionResponseReceivedEvent
@@ -327,6 +335,54 @@ class IntroducerProtocolEngine
s.getRequestTimestamp(), introducee1, introducee2); s.getRequestTimestamp(), introducee1, introducee2);
} }
private IntroducerSession onRemoteResponseInStart(Transaction txn,
IntroducerSession s, AbstractIntroductionMessage m)
throws DbException, FormatException {
// The timestamp must be higher than the last request message
if (m.getTimestamp() <= s.getRequestTimestamp())
return abort(txn, s);
// The dependency, if any, must be the last remote message
if (isInvalidDependency(s, m.getGroupId(), m.getPreviousMessageId()))
return abort(txn, s);
// Mark the response visible in the UI
markMessageVisibleInUi(txn, m.getMessageId());
// Track the incoming message
messageTracker
.trackMessage(txn, m.getGroupId(), m.getTimestamp(), false);
Introducee i = getIntroducee(s, m.getGroupId());
Introducee introducee1, introducee2;
AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId();
Contact c;
if (i.equals(s.getIntroducee1())) {
introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId());
introducee2 = s.getIntroducee2();
c = contactManager
.getContact(txn, s.getIntroducee1().author.getId(),
localAuthorId);
} else if (i.equals(s.getIntroducee2())) {
introducee1 = s.getIntroducee1();
introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId());
c = contactManager
.getContact(txn, s.getIntroducee2().author.getId(),
localAuthorId);
} else throw new AssertionError();
// Broadcast IntroductionResponseReceivedEvent
IntroductionResponse request =
new IntroductionResponse(s.getSessionId(), m.getMessageId(),
m.getGroupId(), INTRODUCER, m.getTimestamp(), false,
false, false, false, c.getAuthor().getName(),
m instanceof AcceptMessage);
IntroductionResponseReceivedEvent e =
new IntroductionResponseReceivedEvent(c.getId(), request);
txn.attach(e);
return new IntroducerSession(s.getSessionId(), START,
s.getRequestTimestamp(), introducee1, introducee2);
}
private IntroducerSession onRemoteAuth(Transaction txn, private IntroducerSession onRemoteAuth(Transaction txn,
IntroducerSession s, AuthMessage m) IntroducerSession s, AuthMessage m)
throws DbException, FormatException { throws DbException, FormatException {
@@ -395,6 +451,9 @@ class IntroducerProtocolEngine
long timestamp = getLocalTimestamp(s, i); long timestamp = getLocalTimestamp(s, i);
Message sent = sendAbortMessage(txn, i, timestamp); Message sent = sendAbortMessage(txn, i, timestamp);
// Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Reset the session back to initial state // Reset the session back to initial state
Introducee introducee1, introducee2; Introducee introducee1, introducee2;
if (i.equals(s.getIntroducee1())) { if (i.equals(s.getIntroducee1())) {
@@ -412,6 +471,10 @@ class IntroducerProtocolEngine
IntroducerSession s) throws DbException, FormatException { IntroducerSession s) throws DbException, FormatException {
// Mark any REQUEST messages in the session unavailable to answer // Mark any REQUEST messages in the session unavailable to answer
markRequestsUnavailableToAnswer(txn, s); markRequestsUnavailableToAnswer(txn, s);
// Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Send an ABORT message to both introducees // Send an ABORT message to both introducees
long timestamp1 = getLocalTimestamp(s, s.getIntroducee1()); long timestamp1 = getLocalTimestamp(s, s.getIntroducee1());
Message sent1 = sendAbortMessage(txn, s.getIntroducee1(), timestamp1); Message sent1 = sendAbortMessage(txn, s.getIntroducee1(), timestamp1);

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -6,12 +6,12 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.Role; import org.briarproject.briar.api.introduction.Role;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
interface IntroductionConstants { interface IntroductionConstants {

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
@@ -24,14 +24,14 @@ import java.util.Map;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_ALICE_MAC_KEY; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_ALICE_MAC_KEY;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_MAC; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_MAC;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_NONCE; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_NONCE;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_SIGN; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_SIGN;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_BOB_MAC_KEY; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_BOB_MAC_KEY;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_MASTER_KEY; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_MASTER_KEY;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID;
import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault

View File

@@ -1,33 +0,0 @@
package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.sync.Group;
import javax.inject.Inject;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
class IntroductionGroupFactory {
private final ContactGroupFactory contactGroupFactory;
private final Group localGroup;
@Inject
IntroductionGroupFactory(ContactGroupFactory contactGroupFactory) {
this.contactGroupFactory = contactGroupFactory;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
CLIENT_VERSION);
}
Group createIntroductionGroup(Contact c) {
return contactGroupFactory.createContactGroup(CLIENT_ID,
CLIENT_VERSION, c);
}
Group createLocalGroup() {
return localGroup;
}
}

View File

@@ -2,19 +2,20 @@ package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager.ContactHook; import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser; import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client; import org.briarproject.bramble.api.sync.Client;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
@@ -24,264 +25,278 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction.IntroducerProtocolState;
import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.introduction.IntroductionMessage; import org.briarproject.briar.api.introduction.IntroductionMessage;
import org.briarproject.briar.api.introduction.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.IntroductionResponse; import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.introduction.Role;
import org.briarproject.briar.client.ConversationClientImpl; import org.briarproject.briar.client.ConversationClientImpl;
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.FINISHED; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED; import static org.briarproject.briar.introduction.IntroductionConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1; import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2; import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT; import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1; import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2; import static org.briarproject.briar.introduction.MessageType.DECLINE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1; import static org.briarproject.briar.introduction.MessageType.REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.RESPONSE_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class IntroductionManagerImpl extends ConversationClientImpl class IntroductionManagerImpl extends ConversationClientImpl
implements IntroductionManager, Client, ContactHook { implements IntroductionManager, Client, ContactHook {
private static final Logger LOG = private final ContactGroupFactory contactGroupFactory;
Logger.getLogger(IntroductionManagerImpl.class.getName()); private final MessageParser messageParser;
private final SessionEncoder sessionEncoder;
private final IntroducerManager introducerManager; private final SessionParser sessionParser;
private final IntroduceeManager introduceeManager; private final IntroducerProtocolEngine introducerEngine;
private final IntroductionGroupFactory introductionGroupFactory; private final IntroduceeProtocolEngine introduceeEngine;
private final IntroductionCrypto crypto;
private final IdentityManager identityManager;
@Inject @Inject
IntroductionManagerImpl(DatabaseComponent db, ClientHelper clientHelper, IntroductionManagerImpl(
MetadataParser metadataParser, MessageTracker messageTracker, DatabaseComponent db,
IntroducerManager introducerManager, ClientHelper clientHelper,
IntroduceeManager introduceeManager, MetadataParser metadataParser,
IntroductionGroupFactory introductionGroupFactory) { MessageTracker messageTracker,
ContactGroupFactory contactGroupFactory,
MessageParser messageParser,
SessionEncoder sessionEncoder,
SessionParser sessionParser,
IntroducerProtocolEngine introducerEngine,
IntroduceeProtocolEngine introduceeEngine,
IntroductionCrypto crypto,
IdentityManager identityManager) {
super(db, clientHelper, metadataParser, messageTracker); super(db, clientHelper, metadataParser, messageTracker);
this.introducerManager = introducerManager; this.contactGroupFactory = contactGroupFactory;
this.introduceeManager = introduceeManager; this.messageParser = messageParser;
this.introductionGroupFactory = introductionGroupFactory; this.sessionEncoder = sessionEncoder;
this.sessionParser = sessionParser;
this.introducerEngine = introducerEngine;
this.introduceeEngine = introduceeEngine;
this.crypto = crypto;
this.identityManager = identityManager;
} }
@Override @Override
public void createLocalState(Transaction txn) throws DbException { public void createLocalState(Transaction txn) throws DbException {
Group localGroup = introductionGroupFactory.createLocalGroup(); // Create a local group to store protocol sessions
Group localGroup = getLocalGroup();
if (db.containsGroup(txn, localGroup.getId())) return; if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup); db.addGroup(txn, localGroup);
// Ensure we've set things up for any pre-existing contacts // Set up groups for communication with any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c); for (Contact c : db.getContacts(txn)) addingContact(txn, c);
} }
@Override @Override
// TODO adapt to use upcoming ClientVersioning client
public void addingContact(Transaction txn, Contact c) throws DbException { public void addingContact(Transaction txn, Contact c) throws DbException {
// Create a group to share with the contact
Group g = getContactGroup(c);
// Return if we've already set things up for this contact
if (db.containsGroup(txn, g.getId())) return;
// Store the group and share it with the contact
db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group
BdfDictionary meta = new BdfDictionary();
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try { try {
// Create an introduction group for sending introduction messages clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
Group g = getContactGroup(c);
// Return if we've already set things up for this contact
if (db.containsGroup(txn, g.getId())) return;
// Store the group and share it with the contact
db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group
BdfDictionary gm = new BdfDictionary();
gm.put(CONTACT, c.getId().getInt());
clientHelper.mergeGroupMetadata(txn, g.getId(), gm);
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new AssertionError(e);
} }
} }
@Override @Override
public void removingContact(Transaction txn, Contact c) throws DbException { public void removingContact(Transaction txn, Contact c) throws DbException {
GroupId gId = introductionGroupFactory.createLocalGroup().getId();
// search for session states where c introduced us
BdfDictionary query = BdfDictionary.of(
new BdfEntry(ROLE, ROLE_INTRODUCEE),
new BdfEntry(CONTACT_ID_1, c.getId().getInt())
);
try { try {
Map<MessageId, BdfDictionary> map = clientHelper removeSessionWithIntroducer(txn, c);
.getMessageMetadataAsDictionary(txn, gId, query); abortOrRemoveSessionWithIntroducee(txn, c);
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
// delete states if introducee removes introducer
deleteMessage(txn, entry.getKey());
}
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); throw new AssertionError();
} }
// Remove the contact group (all messages will be removed with it)
// check for open sessions with c and abort those,
// so the other introducee knows
query = BdfDictionary.of(
new BdfEntry(ROLE, ROLE_INTRODUCER)
);
try {
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, gId, query);
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
BdfDictionary d = entry.getValue();
ContactId c1 = new ContactId(d.getLong(CONTACT_ID_1).intValue());
ContactId c2 = new ContactId(d.getLong(CONTACT_ID_2).intValue());
if (c1.equals(c.getId()) || c2.equals(c.getId())) {
IntroducerProtocolState state = IntroducerProtocolState
.fromValue(d.getLong(STATE).intValue());
// abort protocol if still ongoing
if (IntroducerProtocolState.isOngoing(state)) {
introducerManager.abort(txn, d);
}
// also delete state if both contacts have been deleted
if (c1.equals(c.getId())) {
try {
db.getContact(txn, c2);
} catch (NoSuchContactException e) {
deleteMessage(txn, entry.getKey());
}
} else if (c2.equals(c.getId())) {
try {
db.getContact(txn, c1);
} catch (NoSuchContactException e) {
deleteMessage(txn, entry.getKey());
}
}
}
}
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
// remove the group (all messages will be removed with it)
// this contact won't get our abort message, but the other will
db.removeGroup(txn, getContactGroup(c)); db.removeGroup(txn, getContactGroup(c));
} }
/**
* This is called when a new message arrived and is being validated.
* It is the central method where we determine which role we play
* in the introduction protocol and which engine we need to start.
*/
@Override @Override
protected boolean incomingMessage(Transaction txn, Message m, BdfList body, public Group getContactGroup(Contact c) {
BdfDictionary message) throws DbException, FormatException { return contactGroupFactory
.createContactGroup(CLIENT_ID, CLIENT_VERSION, c);
// Get message data and type
GroupId groupId = m.getGroupId();
long type = message.getLong(TYPE, -1L);
// we are an introducee, need to initialize new state
if (type == TYPE_REQUEST) {
boolean stateExists = true;
try {
getSessionState(txn, groupId, message.getRaw(SESSION_ID), false);
} catch (FormatException e) {
stateExists = false;
}
if (stateExists) throw new FormatException();
BdfDictionary state =
introduceeManager.initialize(txn, groupId, message);
try {
introduceeManager.incomingMessage(txn, state, message);
messageTracker.trackIncomingMessage(txn, m);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
introduceeManager.abort(txn, state);
} catch (FormatException e) {
// FIXME necessary?
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
introduceeManager.abort(txn, state);
}
}
// our role can be anything
else if (type == TYPE_RESPONSE || type == TYPE_ACK || type == TYPE_ABORT) {
BdfDictionary state =
getSessionState(txn, groupId, message.getRaw(SESSION_ID));
long role = state.getLong(ROLE, -1L);
try {
if (role == ROLE_INTRODUCER) {
introducerManager.incomingMessage(txn, state, message);
if (type == TYPE_RESPONSE)
messageTracker.trackIncomingMessage(txn, m);
} else if (role == ROLE_INTRODUCEE) {
introduceeManager.incomingMessage(txn, state, message);
if (type == TYPE_RESPONSE && !message.getBoolean(ACCEPT))
messageTracker.trackIncomingMessage(txn, m);
} else {
if (LOG.isLoggable(WARNING))
LOG.warning("Unknown role '" + role + "'");
throw new DbException();
}
} catch (DbException | FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
if (role == ROLE_INTRODUCER) introducerManager.abort(txn, state);
else introduceeManager.abort(txn, state);
}
} else {
// the message has been validated, so this should not happen
if(LOG.isLoggable(WARNING)) {
LOG.warning("Unknown message type '" + type + "', deleting...");
}
}
return false;
} }
@Override @Override
public Group getContactGroup(Contact contact) { protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
return introductionGroupFactory.createIntroductionGroup(contact); BdfDictionary bdfMeta) throws DbException, FormatException {
// Parse the metadata
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
// Look up the session, if there is one
SessionId sessionId = meta.getSessionId();
IntroduceeSession newIntroduceeSession = null;
if (sessionId == null) {
if (meta.getMessageType() != REQUEST) throw new AssertionError();
newIntroduceeSession = createNewIntroduceeSession(txn, m, body);
sessionId = newIntroduceeSession.getSessionId();
}
StoredSession ss = getSession(txn, sessionId);
// Handle the message
Session session;
MessageId storageId;
if (ss == null) {
if (meta.getMessageType() != REQUEST) throw new FormatException();
if (newIntroduceeSession == null) throw new AssertionError();
storageId = createStorageId(txn);
session = handleMessage(txn, m, body, meta.getMessageType(),
newIntroduceeSession, introduceeEngine);
} else {
storageId = ss.storageId;
Role role = sessionParser.getRole(ss.bdfSession);
if (role == INTRODUCER) {
session = handleMessage(txn, m, body, meta.getMessageType(),
sessionParser.parseIntroducerSession(ss.bdfSession),
introducerEngine);
} else if (role == INTRODUCEE) {
session = handleMessage(txn, m, body, meta.getMessageType(),
sessionParser.parseIntroduceeSession(m.getGroupId(),
ss.bdfSession), introduceeEngine);
} else throw new AssertionError();
}
// Store the updated session
storeSession(txn, storageId, session);
return false;
}
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
Message m, BdfList body) throws DbException, FormatException {
ContactId introducerId = getContactId(txn, m.getGroupId());
Author introducer = db.getContact(txn, introducerId).getAuthor();
Author alice = identityManager.getLocalAuthor(txn);
Author bob = messageParser.parseRequestMessage(m, body).getAuthor();
if (alice.equals(bob)) throw new FormatException();
SessionId sessionId = crypto.getSessionId(introducer, alice, bob);
return IntroduceeSession
.getInitial(m.getGroupId(), sessionId, introducer, bob);
}
private <S extends Session> S handleMessage(Transaction txn, Message m,
BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
throws DbException, FormatException {
if (type == REQUEST) {
RequestMessage request = messageParser.parseRequestMessage(m, body);
return engine.onRequestMessage(txn, session, request);
} else if (type == ACCEPT) {
AcceptMessage accept = messageParser.parseAcceptMessage(m, body);
return engine.onAcceptMessage(txn, session, accept);
} else if (type == DECLINE) {
DeclineMessage decline = messageParser.parseDeclineMessage(m, body);
return engine.onDeclineMessage(txn, session, decline);
} else if (type == AUTH) {
AuthMessage auth = messageParser.parseAuthMessage(m, body);
return engine.onAuthMessage(txn, session, auth);
} else if (type == ACTIVATE) {
ActivateMessage activate =
messageParser.parseActivateMessage(m, body);
return engine.onActivateMessage(txn, session, activate);
} else if (type == ABORT) {
AbortMessage abort = messageParser.parseAbortMessage(m, body);
return engine.onAbortMessage(txn, session, abort);
} else {
throw new AssertionError();
}
}
@Nullable
private StoredSession getSession(Transaction txn,
@Nullable SessionId sessionId) throws DbException, FormatException {
if (sessionId == null) return null;
BdfDictionary query = sessionParser.getSessionQuery(sessionId);
Map<MessageId, BdfDictionary> results = clientHelper
.getMessageMetadataAsDictionary(txn, getLocalGroup().getId(),
query);
if (results.size() > 1) throw new DbException();
if (results.isEmpty()) return null;
return new StoredSession(results.keySet().iterator().next(),
results.values().iterator().next());
}
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException {
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
}
private MessageId createStorageId(Transaction txn) throws DbException {
Message m = clientHelper
.createMessageForStoringMetadata(getLocalGroup().getId());
db.addLocalMessage(txn, m, new Metadata(), false);
return m.getId();
}
private void storeSession(Transaction txn, MessageId storageId,
Session session) throws DbException, FormatException {
BdfDictionary d;
if (session.getRole() == INTRODUCER) {
d = sessionEncoder
.encodeIntroducerSession((IntroducerSession) session);
} else if (session.getRole() == INTRODUCEE) {
d = sessionEncoder
.encodeIntroduceeSession((IntroduceeSession) session);
} else {
throw new AssertionError();
}
clientHelper.mergeMessageMetadata(txn, storageId, d);
} }
@Override @Override
public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg, public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
long timestamp) throws DbException, FormatException { long timestamp) throws DbException {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
introducerManager.makeIntroduction(txn, c1, c2, msg, timestamp); // Look up the session, if there is one
Group g1 = getContactGroup(c1); Author introducer = identityManager.getLocalAuthor(txn);
Group g2 = getContactGroup(c2); SessionId sessionId =
messageTracker.trackMessage(txn, g1.getId(), timestamp, true); crypto.getSessionId(introducer, c1.getAuthor(),
messageTracker.trackMessage(txn, g2.getId(), timestamp, true); c2.getAuthor());
StoredSession ss = getSession(txn, sessionId);
// Create or parse the session
IntroducerSession session;
MessageId storageId;
if (ss == null) {
// This is the first request - create a new session
GroupId groupId1 = getContactGroup(c1).getId();
GroupId groupId2 = getContactGroup(c2).getId();
session = new IntroducerSession(sessionId, groupId1,
c1.getAuthor(), groupId2, c2.getAuthor());
storageId = createStorageId(txn);
} else {
// An earlier request exists, so we already have a session
session = sessionParser.parseIntroducerSession(ss.bdfSession);
storageId = ss.storageId;
}
// Handle the request action
session = introducerEngine
.onRequestAction(txn, session, msg, timestamp);
// Store the updated session
storeSession(txn, storageId, session);
db.commitTransaction(txn); db.commitTransaction(txn);
} catch (FormatException e) {
throw new DbException(e);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
} }
@@ -289,147 +304,78 @@ class IntroductionManagerImpl extends ConversationClientImpl
@Override @Override
public void acceptIntroduction(ContactId contactId, SessionId sessionId, public void acceptIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException, FormatException { long timestamp) throws DbException {
respondToRequest(contactId, sessionId, timestamp, true);
Transaction txn = db.startTransaction(false);
try {
Contact c = db.getContact(txn, contactId);
Group g = getContactGroup(c);
BdfDictionary state =
getSessionState(txn, g.getId(), sessionId.getBytes());
introduceeManager.acceptIntroduction(txn, state, timestamp);
messageTracker.trackMessage(txn, g.getId(), timestamp, true);
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
}
} }
@Override @Override
public void declineIntroduction(ContactId contactId, SessionId sessionId, public void declineIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException, FormatException { long timestamp) throws DbException {
respondToRequest(contactId, sessionId, timestamp, false);
}
private void respondToRequest(ContactId contactId, SessionId sessionId,
long timestamp, boolean accept) throws DbException {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
Contact c = db.getContact(txn, contactId); // Look up the session
Group g = getContactGroup(c); StoredSession ss = getSession(txn, sessionId);
BdfDictionary state = if (ss == null) throw new IllegalArgumentException();
getSessionState(txn, g.getId(), sessionId.getBytes()); // Parse the session
Contact contact = db.getContact(txn, contactId);
introduceeManager.declineIntroduction(txn, state, timestamp); GroupId contactGroupId = getContactGroup(contact).getId();
messageTracker.trackMessage(txn, g.getId(), timestamp, true); IntroduceeSession session = sessionParser
.parseIntroduceeSession(contactGroupId, ss.bdfSession);
// Handle the join or leave action
if (accept) {
session = introduceeEngine
.onAcceptAction(txn, session, timestamp);
} else {
session = introduceeEngine
.onDeclineAction(txn, session, timestamp);
}
// Store the updated session
storeSession(txn, ss.storageId, session);
db.commitTransaction(txn); db.commitTransaction(txn);
} catch (FormatException e) {
throw new DbException(e);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
} }
} }
@Override @Override
public Collection<IntroductionMessage> getIntroductionMessages( public Collection<IntroductionMessage> getIntroductionMessages(ContactId c)
ContactId contactId) throws DbException { throws DbException {
List<IntroductionMessage> messages;
Collection<IntroductionMessage> list = new ArrayList<>();
Map<MessageId, BdfDictionary> metadata;
Collection<MessageStatus> statuses;
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
// get messages and their status Contact contact = db.getContact(txn, c);
GroupId g = getContactGroup(db.getContact(txn, contactId)).getId(); GroupId contactGroupId = getContactGroup(contact).getId();
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g); BdfDictionary query = messageParser.getMessagesVisibleInUiQuery();
statuses = db.getMessageStatus(txn, contactId, g); Map<MessageId, BdfDictionary> results = clientHelper
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
// turn messages into classes for the UI messages = new ArrayList<>(results.size());
for (MessageStatus s : statuses) { for (Map.Entry<MessageId, BdfDictionary> e : results.entrySet()) {
MessageId messageId = s.getMessageId(); MessageId m = e.getKey();
BdfDictionary msg = metadata.get(messageId); MessageMetadata meta =
if (msg == null) continue; messageParser.parseMetadata(e.getValue());
MessageStatus status = db.getMessageStatus(txn, c, m);
try { StoredSession ss = getSession(txn, meta.getSessionId());
long type = msg.getLong(TYPE); if (ss == null) throw new AssertionError();
if (type == TYPE_ACK || type == TYPE_ABORT) continue; MessageType type = meta.getMessageType();
if (type == REQUEST) {
// get session state messages.add(
SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID)); parseInvitationRequest(txn, contactGroupId, m,
BdfDictionary state = meta, status, ss.bdfSession));
getSessionState(txn, g, sessionId.getBytes()); } else if (type == ACCEPT) {
messages.add(
int role = state.getLong(ROLE).intValue(); parseInvitationResponse(txn, contactGroupId, m,
boolean local; meta, status, ss.bdfSession, true));
long time = msg.getLong(MESSAGE_TIME); } else if (type == DECLINE) {
boolean accepted = msg.getBoolean(ACCEPT, false); messages.add(
boolean read = msg.getBoolean(MSG_KEY_READ, false); parseInvitationResponse(txn, contactGroupId, m,
AuthorId authorId; meta, status, ss.bdfSession, false));
String name;
if (type == TYPE_RESPONSE) {
if (role == ROLE_INTRODUCER) {
if (!concernsThisContact(contactId, messageId, state)) {
// this response is not from contactId
continue;
}
local = false;
authorId =
getAuthorIdForIntroducer(contactId, state);
name = getNameForIntroducer(contactId, state);
} else {
if (Arrays.equals(state.getRaw(NOT_OUR_RESPONSE),
messageId.getBytes())) {
// this response is not ours,
// check if it was a decline
if (!accepted) {
local = false;
} else {
// don't include positive responses
continue;
}
} else {
local = true;
}
authorId = new AuthorId(
state.getRaw(REMOTE_AUTHOR_ID));
name = state.getString(NAME);
}
IntroductionResponse ir = new IntroductionResponse(
sessionId, messageId, g, role, time, local,
s.isSent(), s.isSeen(), read, authorId, name,
accepted);
list.add(ir);
} else if (type == TYPE_REQUEST) {
String message;
boolean answered, exists, introducesOtherIdentity;
if (role == ROLE_INTRODUCER) {
local = true;
authorId =
getAuthorIdForIntroducer(contactId, state);
name = getNameForIntroducer(contactId, state);
message = msg.getOptionalString(MSG);
answered = false;
exists = false;
introducesOtherIdentity = false;
} else {
local = false;
authorId = new AuthorId(
state.getRaw(REMOTE_AUTHOR_ID));
name = state.getString(NAME);
message = state.getOptionalString(MSG);
boolean finished = state.getLong(STATE) ==
FINISHED.getValue();
answered = finished || state.getBoolean(ANSWERED);
exists = state.getBoolean(EXISTS);
introducesOtherIdentity =
state.getBoolean(REMOTE_AUTHOR_IS_US);
}
IntroductionRequest ir = new IntroductionRequest(
sessionId, messageId, g, role, time, local,
s.isSent(), s.isSeen(), read, authorId, name,
accepted, message, answered, exists,
introducesOtherIdentity);
list.add(ir);
}
} catch (FormatException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} }
} }
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -438,88 +384,129 @@ class IntroductionManagerImpl extends ConversationClientImpl
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
} }
return list; return messages;
} }
private String getNameForIntroducer(ContactId contactId, private IntroductionRequest parseInvitationRequest(Transaction txn,
BdfDictionary state) throws FormatException { GroupId contactGroupId, MessageId m, MessageMetadata meta,
MessageStatus status, BdfDictionary bdfSession)
if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue())
return state.getString(CONTACT_2);
if (contactId.getInt() == state.getLong(CONTACT_ID_2).intValue())
return state.getString(CONTACT_1);
throw new RuntimeException(
"Contact not part of this introduction session");
}
private AuthorId getAuthorIdForIntroducer(ContactId contactId,
BdfDictionary state) throws FormatException {
if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue())
return new AuthorId(state.getRaw(AUTHOR_ID_2));
if (contactId.getInt() == state.getLong(CONTACT_ID_2).intValue())
return new AuthorId(state.getRaw(AUTHOR_ID_1));
throw new RuntimeException(
"Contact not part of this introduction session");
}
private boolean concernsThisContact(ContactId contactId, MessageId messageId,
BdfDictionary state) throws FormatException {
if (contactId.getInt() == state.getLong(CONTACT_ID_1).intValue()) {
return Arrays.equals(state.getRaw(RESPONSE_1, new byte[0]),
messageId.getBytes());
} else {
return Arrays.equals(state.getRaw(RESPONSE_2, new byte[0]),
messageId.getBytes());
}
}
private BdfDictionary getSessionState(Transaction txn, GroupId groupId,
byte[] sessionId, boolean warn)
throws DbException, FormatException { throws DbException, FormatException {
Role role = sessionParser.getRole(bdfSession);
SessionId sessionId;
Author author;
if (role == INTRODUCER) {
IntroducerSession session =
sessionParser.parseIntroducerSession(bdfSession);
sessionId = session.getSessionId();
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
if (localAuthor.equals(session.getIntroducee1().author)) {
author = session.getIntroducee2().author;
} else {
author = session.getIntroducee1().author;
}
} else if (role == INTRODUCEE) {
IntroduceeSession session = sessionParser
.parseIntroduceeSession(contactGroupId, bdfSession);
sessionId = session.getSessionId();
author = session.getRemoteAuthor();
} else throw new AssertionError();
String message = ""; // TODO
boolean contactExists = false; // TODO
try { return new IntroductionRequest(sessionId, m, contactGroupId,
// See if we can find the state directly for the introducer role, meta.getTimestamp(), meta.isLocal(),
BdfDictionary state = clientHelper status.isSent(), status.isSeen(), meta.isRead(),
.getMessageMetadataAsDictionary(txn, author.getName(), false, message, !meta.isAvailableToAnswer(),
new MessageId(sessionId)); contactExists);
GroupId g1 = new GroupId(state.getRaw(GROUP_ID_1)); }
GroupId g2 = new GroupId(state.getRaw(GROUP_ID_2));
if (!g1.equals(groupId) && !g2.equals(groupId)) { private IntroductionResponse parseInvitationResponse(Transaction txn,
throw new NoSuchMessageException(); GroupId contactGroupId, MessageId m, MessageMetadata meta,
MessageStatus status, BdfDictionary bdfSession, boolean accept)
throws FormatException, DbException {
Role role = sessionParser.getRole(bdfSession);
SessionId sessionId;
Author author;
if (role == INTRODUCER) {
IntroducerSession session =
sessionParser.parseIntroducerSession(bdfSession);
sessionId = session.getSessionId();
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
if (localAuthor.equals(session.getIntroducee1().author)) {
author = session.getIntroducee2().author;
} else {
author = session.getIntroducee1().author;
} }
return state; } else if (role == INTRODUCEE) {
} catch (NoSuchMessageException e) { IntroduceeSession session = sessionParser
// State not found directly, so iterate over all states .parseIntroduceeSession(contactGroupId, bdfSession);
// to find state for introducee sessionId = session.getSessionId();
Map<MessageId, BdfDictionary> map = clientHelper author = session.getRemoteAuthor();
.getMessageMetadataAsDictionary(txn, } else throw new AssertionError();
introductionGroupFactory.createLocalGroup().getId()); return new IntroductionResponse(sessionId, m, contactGroupId,
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { role, meta.getTimestamp(), meta.isLocal(), status.isSent(),
if (Arrays.equals(m.getValue().getRaw(SESSION_ID), sessionId)) { status.isSeen(), meta.isRead(), author.getName(), accept);
BdfDictionary state = m.getValue(); }
GroupId g = new GroupId(state.getRaw(GROUP_ID));
if (g.equals(groupId)) return state; private void removeSessionWithIntroducer(Transaction txn,
} Contact introducer) throws DbException, FormatException {
} BdfDictionary query = sessionEncoder
if (warn && LOG.isLoggable(WARNING)) .getIntroduceeSessionsByIntroducerQuery(introducer.getAuthor());
LOG.warning("No session state found"); Map<MessageId, BdfDictionary> sessions = clientHelper
throw new FormatException(); .getMessageMetadataAsDictionary(txn, getLocalGroup().getId(),
query);
for (MessageId id : sessions.keySet()) {
db.deleteMessageMetadata(txn, id); // TODO needed?
db.removeMessage(txn, id);
} }
} }
private BdfDictionary getSessionState(Transaction txn, GroupId groupId, private void abortOrRemoveSessionWithIntroducee(Transaction txn,
byte[] sessionId) throws DbException, FormatException { Contact c) throws DbException, FormatException {
BdfDictionary query = sessionEncoder.getIntroducerSessionsQuery();
return getSessionState(txn, groupId, sessionId, true); Map<MessageId, BdfDictionary> sessions = clientHelper
.getMessageMetadataAsDictionary(txn, getLocalGroup().getId(),
query);
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
for (Map.Entry<MessageId, BdfDictionary> session : sessions
.entrySet()) {
IntroducerSession s =
sessionParser.parseIntroducerSession(session.getValue());
if (s.getIntroducee1().author.equals(c.getAuthor())) {
abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(),
s.getIntroducee2(), localAuthor);
} else if (s.getIntroducee2().author.equals(c.getAuthor())) {
abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(),
s.getIntroducee1(), localAuthor);
}
}
} }
private void deleteMessage(Transaction txn, MessageId messageId) private void abortOrRemoveSessionWithIntroducee(Transaction txn,
throws DbException { IntroducerSession s, MessageId storageId, Introducee i,
LocalAuthor localAuthor) throws DbException, FormatException {
if (db.containsContact(txn, i.author.getId(), localAuthor.getId())) {
IntroducerSession session = introducerEngine.onAbortAction(txn, s);
storeSession(txn, storageId, session);
} else {
db.deleteMessageMetadata(txn, storageId); // TODO needed?
db.removeMessage(txn, storageId);
}
}
db.deleteMessage(txn, messageId); private Group getLocalGroup() {
db.deleteMessageMetadata(txn, messageId); return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
}
private static class StoredSession {
private final MessageId storageId;
private final BdfDictionary bdfSession;
private StoredSession(MessageId storageId, BdfDictionary bdfSession) {
this.storageId = storageId;
this.bdfSession = bdfSession;
}
} }
} }

View File

@@ -4,8 +4,8 @@ import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.MessageQueueManager;
import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.ConversationManager; import org.briarproject.briar.api.messaging.ConversationManager;
@@ -21,22 +21,22 @@ import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT
public class IntroductionModule { public class IntroductionModule {
public static class EagerSingletons { public static class EagerSingletons {
@Inject
IntroductionManager introductionManager;
@Inject @Inject
IntroductionValidator introductionValidator; IntroductionValidator introductionValidator;
@Inject
IntroductionManager introductionManager;
} }
@Provides @Provides
@Singleton @Singleton
IntroductionValidator provideValidator( IntroductionValidator provideValidator(ValidationManager validationManager,
MessageQueueManager messageQueueManager, MessageEncoder messageEncoder, MetadataEncoder metadataEncoder,
MetadataEncoder metadataEncoder, ClientHelper clientHelper, ClientHelper clientHelper, Clock clock) {
Clock clock) {
IntroductionValidator introductionValidator = new IntroductionValidator( IntroductionValidator introductionValidator =
clientHelper, metadataEncoder, clock); new IntroductionValidator(messageEncoder, clientHelper,
messageQueueManager.registerMessageValidator(CLIENT_ID, metadataEncoder, clock);
validationManager.registerMessageValidator(CLIENT_ID,
introductionValidator); introductionValidator);
return introductionValidator; return introductionValidator;
@@ -46,16 +46,42 @@ public class IntroductionModule {
@Singleton @Singleton
IntroductionManager provideIntroductionManager( IntroductionManager provideIntroductionManager(
LifecycleManager lifecycleManager, ContactManager contactManager, LifecycleManager lifecycleManager, ContactManager contactManager,
MessageQueueManager messageQueueManager, ValidationManager validationManager,
ConversationManager conversationManager, ConversationManager conversationManager,
IntroductionManagerImpl introductionManager) { IntroductionManagerImpl introductionManager) {
lifecycleManager.registerClient(introductionManager); lifecycleManager.registerClient(introductionManager);
contactManager.registerContactHook(introductionManager); contactManager.registerContactHook(introductionManager);
messageQueueManager.registerIncomingMessageHook(CLIENT_ID, validationManager.registerIncomingMessageHook(CLIENT_ID,
introductionManager); introductionManager);
conversationManager.registerConversationClient(introductionManager); conversationManager.registerConversationClient(introductionManager);
return introductionManager; return introductionManager;
} }
@Provides
MessageParser provideMessageParser(MessageParserImpl messageParser) {
return messageParser;
}
@Provides
MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
return messageEncoder;
}
@Provides
SessionParser provideSessionParser(SessionParserImpl sessionParser) {
return sessionParser;
}
@Provides
SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
return sessionEncoder;
}
@Provides
IntroductionCrypto provideIntroductionCrypto(
IntroductionCryptoImpl introductionCrypto) {
return introductionCrypto;
}
} }

View File

@@ -1,7 +1,9 @@
package org.briarproject.briar.introduction; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.client.BdfMessageContext; import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.client.BdfMessageValidator;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
@@ -9,183 +11,166 @@ import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.client.BdfQueueMessageValidator;
import java.util.Collections;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.bramble.util.ValidationUtils.checkSize;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY; import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class IntroductionValidator extends BdfQueueMessageValidator { class IntroductionValidator extends BdfMessageValidator {
IntroductionValidator(ClientHelper clientHelper, private final MessageEncoder messageEncoder;
MetadataEncoder metadataEncoder, Clock clock) {
IntroductionValidator(MessageEncoder messageEncoder,
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock) {
super(clientHelper, metadataEncoder, clock); super(clientHelper, metadataEncoder, clock);
this.messageEncoder = messageEncoder;
} }
@Override @Override
protected BdfMessageContext validateMessage(Message m, Group g, protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException { BdfList body) throws FormatException {
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
BdfDictionary d; switch (type) {
long type = body.getLong(0); case REQUEST:
byte[] id = body.getRaw(1); return validateRequestMessage(m, body);
checkLength(id, SessionId.LENGTH); case ACCEPT:
return validateAcceptMessage(m, body);
case AUTH:
return validateAuthMessage(m, body);
case DECLINE:
case ACTIVATE:
case ABORT:
return validateOtherMessage(type, m, body);
default:
throw new FormatException();
}
}
if (type == TYPE_REQUEST) { private BdfMessageContext validateRequestMessage(Message m, BdfList body)
d = validateRequest(body); throws FormatException {
} else if (type == TYPE_RESPONSE) { checkSize(body, 4);
d = validateResponse(body);
} else if (type == TYPE_ACK) { byte[] previousMessageId = body.getOptionalRaw(1);
d = validateAck(body); checkLength(previousMessageId, UniqueId.LENGTH);
} else if (type == TYPE_ABORT) {
d = validateAbort(body); BdfList authorList = body.getList(2);
clientHelper.parseAndValidateAuthor(authorList);
String msg = body.getOptionalString(3);
checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH);
BdfDictionary meta = messageEncoder
.encodeRequestMetadata(m.getTimestamp(), false, false,
false, false);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} else { } else {
throw new FormatException(); MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta,
Collections.singletonList(dependency));
} }
d.put(TYPE, type);
d.put(SESSION_ID, id);
d.put(GROUP_ID, m.getGroupId());
d.put(MESSAGE_ID, m.getId());
d.put(MESSAGE_TIME, m.getTimestamp());
return new BdfMessageContext(d);
} }
private BdfDictionary validateRequest(BdfList message) private BdfMessageContext validateAcceptMessage(Message m, BdfList body)
throws FormatException { throws FormatException {
checkSize(body, 6);
checkSize(message, 4, 5); byte[] sessionIdBytes = body.getRaw(1);
checkLength(sessionIdBytes, UniqueId.LENGTH);
// TODO: Exchange author format version byte[] previousMessageId = body.getOptionalRaw(2);
checkLength(previousMessageId, UniqueId.LENGTH);
// parse contact name byte[] ephemeralPublicKey = body.getRaw(3);
String name = message.getString(2); checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
// parse contact's public key body.getLong(4);
byte[] key = message.getRaw(3);
checkLength(key, 0, MAX_PUBLIC_KEY_LENGTH);
// parse (optional) message BdfDictionary transportProperties = body.getDictionary(5);
String msg = null; if (transportProperties.size() < 1) throw new FormatException();
if (message.size() == 5) { for (String tId : transportProperties.keySet()) {
msg = message.getString(4); checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH);
checkLength(msg, 0, MAX_INTRODUCTION_MESSAGE_LENGTH); BdfDictionary tProps = transportProperties.getDictionary(tId);
clientHelper.parseAndValidateTransportProperties(tProps);
} }
// Return the metadata SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary d = new BdfDictionary(); BdfDictionary meta = messageEncoder
d.put(NAME, name); .encodeMetadata(ACCEPT, sessionId, m.getTimestamp(), false,
d.put(PUBLIC_KEY, key); false, false);
if (msg != null) { if (previousMessageId == null) {
d.put(MSG, msg); return new BdfMessageContext(meta);
}
return d;
}
private BdfDictionary validateResponse(BdfList message)
throws FormatException {
checkSize(message, 3, 6);
// parse accept/decline
boolean accept = message.getBoolean(2);
long time = 0;
byte[] pubkey = null;
BdfDictionary tp = new BdfDictionary();
if (accept) {
checkSize(message, 6);
// parse timestamp
time = message.getLong(3);
// parse ephemeral public key
pubkey = message.getRaw(4);
checkLength(pubkey, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
// parse transport properties
tp = message.getDictionary(5);
if (tp.size() < 1) throw new FormatException();
for (String tId : tp.keySet()) {
checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH);
BdfDictionary tProps = tp.getDictionary(tId);
checkSize(tProps, 0, MAX_PROPERTIES_PER_TRANSPORT);
for (String propId : tProps.keySet()) {
checkLength(propId, 0, MAX_PROPERTY_LENGTH);
String prop = tProps.getString(propId);
checkLength(prop, 0, MAX_PROPERTY_LENGTH);
}
}
} else { } else {
checkSize(message, 3); MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta,
Collections.singletonList(dependency));
} }
// Return the metadata
BdfDictionary d = new BdfDictionary();
d.put(ACCEPT, accept);
if (accept) {
d.put(TIME, time);
d.put(E_PUBLIC_KEY, pubkey);
d.put(TRANSPORT, tp);
}
return d;
} }
private BdfDictionary validateAck(BdfList message) throws FormatException { private BdfMessageContext validateAuthMessage(Message m, BdfList body)
checkSize(message, 4);
byte[] mac = message.getRaw(2);
checkLength(mac, 1, MAC_LENGTH);
byte[] sig = message.getRaw(3);
checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
// Return the metadata
BdfDictionary d = new BdfDictionary();
d.put(MAC, mac);
d.put(SIGNATURE, sig);
return d;
}
private BdfDictionary validateAbort(BdfList message)
throws FormatException { throws FormatException {
checkSize(body, 5);
checkSize(message, 2); byte[] sessionIdBytes = body.getRaw(1);
checkLength(sessionIdBytes, UniqueId.LENGTH);
// Return the metadata byte[] previousMessageId = body.getRaw(2);
return new BdfDictionary(); checkLength(previousMessageId, UniqueId.LENGTH);
byte[] mac = body.getRaw(3);
checkLength(mac, MAC_BYTES);
byte[] signature = body.getRaw(4);
checkLength(signature, 1, MAX_SIGNATURE_BYTES);
SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder
.encodeMetadata(AUTH, sessionId, m.getTimestamp(), false, false,
false);
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta,
Collections.singletonList(dependency));
} }
private BdfMessageContext validateOtherMessage(MessageType type,
Message m, BdfList body) throws FormatException {
checkSize(body, 3);
byte[] sessionIdBytes = body.getRaw(1);
checkLength(sessionIdBytes, UniqueId.LENGTH);
byte[] previousMessageId = body.getOptionalRaw(2);
checkLength(previousMessageId, UniqueId.LENGTH);
SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder
.encodeMetadata(type, sessionId, m.getTimestamp(), false, false,
false);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} else {
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta,
Collections.singletonList(dependency));
}
}
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
@@ -20,19 +20,19 @@ import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
import static org.briarproject.briar.introduction2.MessageType.ABORT; import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.introduction2.MessageType.ACCEPT; import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction2.MessageType.ACTIVATE; import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
import static org.briarproject.briar.introduction2.MessageType.AUTH; import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.introduction2.MessageType.DECLINE; import static org.briarproject.briar.introduction.MessageType.DECLINE;
import static org.briarproject.briar.introduction2.MessageType.REQUEST; import static org.briarproject.briar.introduction.MessageType.REQUEST;
@NotNullByDefault @NotNullByDefault
class MessageEncoderImpl implements MessageEncoder { class MessageEncoderImpl implements MessageEncoder {

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
@@ -18,14 +18,14 @@ import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI; import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
import static org.briarproject.briar.introduction2.MessageType.REQUEST; import static org.briarproject.briar.introduction.MessageType.REQUEST;
@NotNullByDefault @NotNullByDefault
class MessageParserImpl implements MessageParser { class MessageParserImpl implements MessageParser {

View File

@@ -1,125 +0,0 @@
package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.MessageQueueManager;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
@Immutable
@NotNullByDefault
class MessageSender {
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final Clock clock;
private final MetadataEncoder metadataEncoder;
private final MessageQueueManager messageQueueManager;
@Inject
MessageSender(DatabaseComponent db, ClientHelper clientHelper, Clock clock,
MetadataEncoder metadataEncoder,
MessageQueueManager messageQueueManager) {
this.db = db;
this.clientHelper = clientHelper;
this.clock = clock;
this.metadataEncoder = metadataEncoder;
this.messageQueueManager = messageQueueManager;
}
void sendMessage(Transaction txn, BdfDictionary message)
throws DbException, FormatException {
BdfList bdfList = encodeMessage(message);
byte[] body = clientHelper.toByteArray(bdfList);
GroupId groupId = new GroupId(message.getRaw(GROUP_ID));
Group group = db.getGroup(txn, groupId);
long timestamp = clock.currentTimeMillis();
message.put(MESSAGE_TIME, timestamp);
Metadata metadata = metadataEncoder.encode(message);
messageQueueManager.sendMessage(txn, group, timestamp, body, metadata);
}
private BdfList encodeMessage(BdfDictionary d) throws FormatException {
BdfList body;
long type = d.getLong(TYPE);
if (type == TYPE_REQUEST) {
body = encodeRequest(d);
} else if (type == TYPE_RESPONSE) {
body = encodeResponse(d);
} else if (type == TYPE_ACK) {
body = encodeAck(d);
} else if (type == TYPE_ABORT) {
body = encodeAbort(d);
} else {
throw new FormatException();
}
return body;
}
private BdfList encodeRequest(BdfDictionary d) throws FormatException {
BdfList list = BdfList.of(TYPE_REQUEST, d.getRaw(SESSION_ID),
d.getString(NAME), d.getRaw(PUBLIC_KEY));
if (d.containsKey(MSG)) {
list.add(d.getString(MSG));
}
return list;
}
private BdfList encodeResponse(BdfDictionary d) throws FormatException {
BdfList list = BdfList.of(TYPE_RESPONSE, d.getRaw(SESSION_ID),
d.getBoolean(ACCEPT));
if (d.getBoolean(ACCEPT)) {
list.add(d.getLong(TIME));
list.add(d.getRaw(E_PUBLIC_KEY));
list.add(d.getDictionary(TRANSPORT));
}
return list;
}
private BdfList encodeAck(BdfDictionary d) throws FormatException {
return BdfList.of(TYPE_ACK, d.getRaw(SESSION_ID), d.getRaw(MAC),
d.getRaw(SIGNATURE));
}
private BdfList encodeAbort(BdfDictionary d) throws FormatException {
return BdfList.of(TYPE_ABORT, d.getRaw(SESSION_ID));
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class RequestMessage extends IntroductionMessage { class RequestMessage extends AbstractIntroductionMessage {
private final Author author; private final Author author;
@Nullable @Nullable

View File

@@ -1,8 +1,8 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.Role; import org.briarproject.briar.api.introduction.Role;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;

View File

@@ -1,11 +1,16 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault @NotNullByDefault
interface SessionEncoder { interface SessionEncoder {
BdfDictionary getIntroduceeSessionsByIntroducerQuery(Author introducer);
BdfDictionary getIntroducerSessionsQuery();
BdfDictionary encodeIntroducerSession(IntroducerSession s); BdfDictionary encodeIntroducerSession(IntroducerSession s);
BdfDictionary encodeIntroduceeSession(IntroduceeSession s); BdfDictionary encodeIntroduceeSession(IntroduceeSession s);

View File

@@ -1,11 +1,13 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.briar.introduction2.IntroducerSession.Introducee; import org.briarproject.briar.introduction.IntroducerSession.Introducee;
import java.util.Map; import java.util.Map;
@@ -14,28 +16,30 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_AUTHOR; import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_GROUP_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_GROUP_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCER; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_MASTER_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_SESSION_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_STATE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_STATE;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -48,6 +52,23 @@ class SessionEncoderImpl implements SessionEncoder {
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
} }
@Override
public BdfDictionary getIntroduceeSessionsByIntroducerQuery(
Author introducer) {
return BdfDictionary.of(
new BdfEntry(SESSION_KEY_ROLE, INTRODUCEE.getValue()),
new BdfEntry(SESSION_KEY_INTRODUCER,
clientHelper.toList(introducer))
);
}
@Override
public BdfDictionary getIntroducerSessionsQuery() {
return BdfDictionary.of(
new BdfEntry(SESSION_KEY_ROLE, INTRODUCER.getValue())
);
}
@Override @Override
public BdfDictionary encodeIntroducerSession(IntroducerSession s) { public BdfDictionary encodeIntroducerSession(IntroducerSession s) {
BdfDictionary d = encodeSession(s); BdfDictionary d = encodeSession(s);

View File

@@ -1,11 +1,11 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.Role; import org.briarproject.briar.api.introduction.Role;
@NotNullByDefault @NotNullByDefault
interface SessionParser { interface SessionParser {

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
@@ -12,8 +12,8 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.Role; import org.briarproject.briar.api.introduction.Role;
import org.briarproject.briar.introduction2.IntroducerSession.Introducee; import org.briarproject.briar.introduction.IntroducerSession.Introducee;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -22,30 +22,30 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_AUTHOR; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_GROUP_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_GROUP_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_1; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_2; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCER; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_MASTER_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_SESSION_ID; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_STATE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_STATE;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault

View File

@@ -0,0 +1,7 @@
package org.briarproject.briar.introduction;
interface State {
int getValue();
}

View File

@@ -1,459 +0,0 @@
package org.briarproject.briar.introduction2;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.IntroductionManager;
import org.briarproject.briar.api.introduction2.IntroductionMessage;
import org.briarproject.briar.api.introduction2.IntroductionRequest;
import org.briarproject.briar.api.introduction2.IntroductionResponse;
import org.briarproject.briar.api.introduction2.Role;
import org.briarproject.briar.client.ConversationClientImpl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
import static org.briarproject.briar.introduction2.IntroductionConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.introduction2.MessageType.ABORT;
import static org.briarproject.briar.introduction2.MessageType.ACCEPT;
import static org.briarproject.briar.introduction2.MessageType.ACTIVATE;
import static org.briarproject.briar.introduction2.MessageType.AUTH;
import static org.briarproject.briar.introduction2.MessageType.DECLINE;
import static org.briarproject.briar.introduction2.MessageType.REQUEST;
@Immutable
@NotNullByDefault
class IntroductionManagerImpl extends ConversationClientImpl
implements IntroductionManager, Client, ContactHook {
private final ContactGroupFactory contactGroupFactory;
private final MessageParser messageParser;
private final SessionEncoder sessionEncoder;
private final SessionParser sessionParser;
private final IntroducerProtocolEngine introducerEngine;
private final IntroduceeProtocolEngine introduceeEngine;
private final IntroductionCrypto crypto;
private final IdentityManager identityManager;
@Inject
IntroductionManagerImpl(
DatabaseComponent db,
ClientHelper clientHelper,
MetadataParser metadataParser,
MessageTracker messageTracker,
ContactGroupFactory contactGroupFactory,
MessageParser messageParser,
SessionEncoder sessionEncoder,
SessionParser sessionParser,
IntroducerProtocolEngine introducerEngine,
IntroduceeProtocolEngine introduceeEngine,
IntroductionCrypto crypto,
IdentityManager identityManager) {
super(db, clientHelper, metadataParser, messageTracker);
this.contactGroupFactory = contactGroupFactory;
this.messageParser = messageParser;
this.sessionEncoder = sessionEncoder;
this.sessionParser = sessionParser;
this.introducerEngine = introducerEngine;
this.introduceeEngine = introduceeEngine;
this.crypto = crypto;
this.identityManager = identityManager;
}
@Override
public void createLocalState(Transaction txn) throws DbException {
// Create a local group to store protocol sessions
Group localGroup = getLocalGroup();
if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup);
// Set up groups for communication with any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
}
@Override
// TODO adapt to use upcoming ClientVersioning client
public void addingContact(Transaction txn, Contact c) throws DbException {
// Create a group to share with the contact
Group g = getContactGroup(c);
// Return if we've already set things up for this contact
if (db.containsGroup(txn, g.getId())) return;
// Store the group and share it with the contact
db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group
BdfDictionary meta = new BdfDictionary();
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
// Remove the contact group (all messages will be removed with it)
db.removeGroup(txn, getContactGroup(c));
// TODO abort other sessions the contact is involved in
}
@Override
public Group getContactGroup(Contact c) {
return contactGroupFactory
.createContactGroup(CLIENT_ID, CLIENT_VERSION, c);
}
@Override
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
BdfDictionary bdfMeta) throws DbException, FormatException {
// Parse the metadata
MessageMetadata meta = messageParser.parseMetadata(bdfMeta);
// Look up the session, if there is one
SessionId sessionId = meta.getSessionId();
IntroduceeSession newIntroduceeSession = null;
if (sessionId == null) {
if (meta.getMessageType() != REQUEST) throw new AssertionError();
newIntroduceeSession = createNewIntroduceeSession(txn, m, body);
sessionId = newIntroduceeSession.getSessionId();
}
StoredSession ss = getSession(txn, sessionId);
// Handle the message
Session session;
MessageId storageId;
if (ss == null) {
if (meta.getMessageType() != REQUEST) throw new FormatException();
if (newIntroduceeSession == null) throw new AssertionError();
storageId = createStorageId(txn);
session = handleMessage(txn, m, body, meta.getMessageType(),
newIntroduceeSession, introduceeEngine);
} else {
storageId = ss.storageId;
Role role = sessionParser.getRole(ss.bdfSession);
if (role == INTRODUCER) {
session = handleMessage(txn, m, body, meta.getMessageType(),
sessionParser.parseIntroducerSession(ss.bdfSession),
introducerEngine);
} else if (role == INTRODUCEE) {
session = handleMessage(txn, m, body, meta.getMessageType(),
sessionParser.parseIntroduceeSession(m.getGroupId(),
ss.bdfSession), introduceeEngine);
} else throw new AssertionError();
}
// Store the updated session
storeSession(txn, storageId, session);
return false;
}
private IntroduceeSession createNewIntroduceeSession(Transaction txn,
Message m, BdfList body) throws DbException, FormatException {
ContactId introducerId = getContactId(txn, m.getGroupId());
Author introducer = db.getContact(txn, introducerId).getAuthor();
Author alice = identityManager.getLocalAuthor(txn);
Author bob = messageParser.parseRequestMessage(m, body).getAuthor();
SessionId sessionId = crypto.getSessionId(introducer, alice, bob);
return IntroduceeSession
.getInitial(m.getGroupId(), sessionId, introducer, bob);
}
private <S extends Session> S handleMessage(Transaction txn, Message m,
BdfList body, MessageType type, S session, ProtocolEngine<S> engine)
throws DbException, FormatException {
if (type == REQUEST) {
RequestMessage request = messageParser.parseRequestMessage(m, body);
return engine.onRequestMessage(txn, session, request);
} else if (type == ACCEPT) {
AcceptMessage accept = messageParser.parseAcceptMessage(m, body);
return engine.onAcceptMessage(txn, session, accept);
} else if (type == DECLINE) {
DeclineMessage decline = messageParser.parseDeclineMessage(m, body);
return engine.onDeclineMessage(txn, session, decline);
} else if (type == AUTH) {
AuthMessage auth = messageParser.parseAuthMessage(m, body);
return engine.onAuthMessage(txn, session, auth);
} else if (type == ACTIVATE) {
ActivateMessage activate =
messageParser.parseActivateMessage(m, body);
return engine.onActivateMessage(txn, session, activate);
} else if (type == ABORT) {
AbortMessage abort = messageParser.parseAbortMessage(m, body);
return engine.onAbortMessage(txn, session, abort);
} else {
throw new AssertionError();
}
}
@Nullable
private StoredSession getSession(Transaction txn,
@Nullable SessionId sessionId) throws DbException, FormatException {
if (sessionId == null) return null;
BdfDictionary query = sessionParser.getSessionQuery(sessionId);
Map<MessageId, BdfDictionary> results = clientHelper
.getMessageMetadataAsDictionary(txn, getLocalGroup().getId(),
query);
if (results.size() > 1) throw new DbException();
if (results.isEmpty()) return null;
return new StoredSession(results.keySet().iterator().next(),
results.values().iterator().next());
}
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException {
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
}
private MessageId createStorageId(Transaction txn) throws DbException {
Message m = clientHelper
.createMessageForStoringMetadata(getLocalGroup().getId());
db.addLocalMessage(txn, m, new Metadata(), false);
return m.getId();
}
private void storeSession(Transaction txn, MessageId storageId,
Session session) throws DbException, FormatException {
BdfDictionary d;
if (session.getRole() == INTRODUCER) {
d = sessionEncoder
.encodeIntroducerSession((IntroducerSession) session);
} else if (session.getRole() == INTRODUCEE) {
d = sessionEncoder
.encodeIntroduceeSession((IntroduceeSession) session);
} else {
throw new AssertionError();
}
clientHelper.mergeMessageMetadata(txn, storageId, d);
}
@Override
public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
long timestamp) throws DbException {
Transaction txn = db.startTransaction(false);
try {
// Look up the session, if there is one
Author introducer = identityManager.getLocalAuthor(txn);
SessionId sessionId =
crypto.getSessionId(introducer, c1.getAuthor(),
c2.getAuthor());
StoredSession ss = getSession(txn, sessionId);
// Create or parse the session
IntroducerSession session;
MessageId storageId;
if (ss == null) {
// This is the first request - create a new session
GroupId groupId1 = getContactGroup(c1).getId();
GroupId groupId2 = getContactGroup(c2).getId();
session = new IntroducerSession(sessionId, groupId1,
c1.getAuthor(), groupId2, c2.getAuthor());
storageId = createStorageId(txn);
} else {
// An earlier request exists, so we already have a session
session = sessionParser.parseIntroducerSession(ss.bdfSession);
storageId = ss.storageId;
}
// Handle the request action
session = introducerEngine
.onRequestAction(txn, session, msg, timestamp);
// Store the updated session
storeSession(txn, storageId, session);
db.commitTransaction(txn);
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
}
@Override
public void acceptIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException {
respondToRequest(contactId, sessionId, timestamp, true);
}
@Override
public void declineIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException {
respondToRequest(contactId, sessionId, timestamp, false);
}
private void respondToRequest(ContactId contactId, SessionId sessionId,
long timestamp, boolean accept) throws DbException {
Transaction txn = db.startTransaction(false);
try {
// Look up the session
StoredSession ss = getSession(txn, sessionId);
if (ss == null) throw new IllegalArgumentException();
// Parse the session
Contact contact = db.getContact(txn, contactId);
GroupId contactGroupId = getContactGroup(contact).getId();
IntroduceeSession session = sessionParser
.parseIntroduceeSession(contactGroupId, ss.bdfSession);
// Handle the join or leave action
if (accept) {
session = introduceeEngine
.onAcceptAction(txn, session, timestamp);
} else {
session = introduceeEngine
.onDeclineAction(txn, session, timestamp);
}
// Store the updated session
storeSession(txn, ss.storageId, session);
db.commitTransaction(txn);
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
}
@Override
public Collection<IntroductionMessage> getIntroductionMessages(ContactId c)
throws DbException {
List<IntroductionMessage> messages;
Transaction txn = db.startTransaction(true);
try {
Contact contact = db.getContact(txn, c);
GroupId contactGroupId = getContactGroup(contact).getId();
BdfDictionary query = messageParser.getMessagesVisibleInUiQuery();
Map<MessageId, BdfDictionary> results = clientHelper
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
messages = new ArrayList<>(results.size());
for (Map.Entry<MessageId, BdfDictionary> e : results.entrySet()) {
MessageId m = e.getKey();
MessageMetadata meta =
messageParser.parseMetadata(e.getValue());
MessageStatus status = db.getMessageStatus(txn, c, m);
StoredSession ss = getSession(txn, meta.getSessionId());
if (ss == null) throw new AssertionError();
MessageType type = meta.getMessageType();
if (type == REQUEST) {
messages.add(
parseInvitationRequest(txn, contactGroupId, m,
meta, status, ss.bdfSession));
} else if (type == ACCEPT) {
messages.add(
parseInvitationResponse(txn, contactGroupId, m,
meta, status, ss.bdfSession, true));
} else if (type == DECLINE) {
messages.add(
parseInvitationResponse(txn, contactGroupId, m,
meta, status, ss.bdfSession, false));
}
}
db.commitTransaction(txn);
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
return messages;
}
private IntroductionRequest parseInvitationRequest(Transaction txn,
GroupId contactGroupId, MessageId m, MessageMetadata meta,
MessageStatus status, BdfDictionary bdfSession)
throws DbException, FormatException {
Role role = sessionParser.getRole(bdfSession);
SessionId sessionId;
Author author;
if (role == INTRODUCER) {
IntroducerSession session =
sessionParser.parseIntroducerSession(bdfSession);
sessionId = session.getSessionId();
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
if (localAuthor.equals(session.getIntroducee1().author)) {
author = session.getIntroducee2().author;
} else {
author = session.getIntroducee1().author;
}
} else if (role == INTRODUCEE) {
IntroduceeSession session = sessionParser
.parseIntroduceeSession(contactGroupId, bdfSession);
sessionId = session.getSessionId();
author = session.getRemoteAuthor();
} else throw new AssertionError();
String message = ""; // TODO
boolean contactExists = false; // TODO
return new IntroductionRequest(sessionId, m, contactGroupId,
role, meta.getTimestamp(), meta.isLocal(),
status.isSent(), status.isSeen(), meta.isRead(),
author.getName(), false, message, !meta.isAvailableToAnswer(),
contactExists);
}
private IntroductionResponse parseInvitationResponse(Transaction txn,
GroupId contactGroupId, MessageId m, MessageMetadata meta,
MessageStatus status, BdfDictionary bdfSession, boolean accept)
throws FormatException, DbException {
Role role = sessionParser.getRole(bdfSession);
SessionId sessionId;
Author author;
if (role == INTRODUCER) {
IntroducerSession session =
sessionParser.parseIntroducerSession(bdfSession);
sessionId = session.getSessionId();
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
if (localAuthor.equals(session.getIntroducee1().author)) {
author = session.getIntroducee2().author;
} else {
author = session.getIntroducee1().author;
}
} else if (role == INTRODUCEE) {
IntroduceeSession session = sessionParser
.parseIntroduceeSession(contactGroupId, bdfSession);
sessionId = session.getSessionId();
author = session.getRemoteAuthor();
} else throw new AssertionError();
return new IntroductionResponse(sessionId, m, contactGroupId,
role, meta.getTimestamp(), meta.isLocal(), status.isSent(),
status.isSeen(), meta.isRead(), author.getName(), accept);
}
private Group getLocalGroup() {
return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
}
private static class StoredSession {
private final MessageId storageId;
private final BdfDictionary bdfSession;
private StoredSession(MessageId storageId, BdfDictionary bdfSession) {
this.storageId = storageId;
this.bdfSession = bdfSession;
}
}
}

View File

@@ -1,39 +0,0 @@
package org.briarproject.briar.introduction2;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
@Module
public class IntroductionModule {
public static class EagerSingletons {
@Inject
IntroductionValidator introductionValidator;
}
@Provides
@Singleton
IntroductionValidator provideValidator(ValidationManager validationManager,
MessageEncoder messageEncoder, MetadataEncoder metadataEncoder,
ClientHelper clientHelper, Clock clock) {
IntroductionValidator introductionValidator =
new IntroductionValidator(messageEncoder, clientHelper,
metadataEncoder, clock);
validationManager.registerMessageValidator(CLIENT_ID,
introductionValidator);
return introductionValidator;
}
}

View File

@@ -1,168 +0,0 @@
package org.briarproject.briar.introduction2;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.client.BdfMessageValidator;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.SessionId;
import java.util.Collections;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
import static org.briarproject.briar.introduction2.MessageType.ACCEPT;
import static org.briarproject.briar.introduction2.MessageType.AUTH;
@Immutable
@NotNullByDefault
class IntroductionValidator extends BdfMessageValidator {
private final MessageEncoder messageEncoder;
IntroductionValidator(MessageEncoder messageEncoder,
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock) {
super(clientHelper, metadataEncoder, clock);
this.messageEncoder = messageEncoder;
}
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
switch (type) {
case REQUEST:
return validateRequestMessage(m, body);
case ACCEPT:
return validateAcceptMessage(m, body);
case AUTH:
return validateAuthMessage(m, body);
case DECLINE:
case ACTIVATE:
case ABORT:
return validateOtherMessage(type, m, body);
default:
throw new FormatException();
}
}
private BdfMessageContext validateRequestMessage(Message m, BdfList body)
throws FormatException {
checkSize(body, 4);
byte[] previousMessageId = body.getOptionalRaw(1);
checkLength(previousMessageId, UniqueId.LENGTH);
BdfList authorList = body.getList(2);
clientHelper.parseAndValidateAuthor(authorList);
String msg = body.getOptionalString(3);
checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH);
BdfDictionary meta = messageEncoder
.encodeRequestMetadata(m.getTimestamp(), false, false,
false, false);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} else {
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta,
Collections.singletonList(dependency));
}
}
private BdfMessageContext validateAcceptMessage(Message m, BdfList body)
throws FormatException {
checkSize(body, 6);
byte[] sessionIdBytes = body.getRaw(1);
checkLength(sessionIdBytes, UniqueId.LENGTH);
byte[] previousMessageId = body.getRaw(2);
checkLength(previousMessageId, UniqueId.LENGTH);
byte[] ephemeralPublicKey = body.getRaw(3);
checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH);
body.getLong(4);
BdfDictionary transportProperties = body.getDictionary(5);
if (transportProperties.size() < 1) throw new FormatException();
for (String tId : transportProperties.keySet()) {
checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH);
BdfDictionary tProps = transportProperties.getDictionary(tId);
clientHelper.parseAndValidateTransportProperties(tProps);
}
SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder
.encodeMetadata(ACCEPT, sessionId, m.getTimestamp(), false,
false, false);
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta,
Collections.singletonList(dependency));
}
private BdfMessageContext validateAuthMessage(Message m, BdfList body)
throws FormatException {
checkSize(body, 5);
byte[] sessionIdBytes = body.getRaw(1);
checkLength(sessionIdBytes, UniqueId.LENGTH);
byte[] previousMessageId = body.getRaw(2);
checkLength(previousMessageId, UniqueId.LENGTH);
byte[] mac = body.getRaw(3);
checkLength(mac, MAC_BYTES);
byte[] signature = body.getRaw(4);
checkLength(signature, 1, MAX_SIGNATURE_BYTES);
SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder
.encodeMetadata(AUTH, sessionId, m.getTimestamp(), false, false,
false);
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta,
Collections.singletonList(dependency));
}
private BdfMessageContext validateOtherMessage(MessageType type,
Message m, BdfList body) throws FormatException {
checkSize(body, 3);
byte[] sessionIdBytes = body.getRaw(1);
checkLength(sessionIdBytes, UniqueId.LENGTH);
byte[] previousMessageId = body.getRaw(2);
checkLength(previousMessageId, UniqueId.LENGTH);
SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder
.encodeMetadata(type, sessionId, m.getTimestamp(), false, false,
false);
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta,
Collections.singletonList(dependency));
}
}

View File

@@ -1,7 +0,0 @@
package org.briarproject.briar.introduction2;
interface State {
int getValue();
}

View File

@@ -1,424 +0,0 @@
package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction.IntroduceeProtocolState;
import org.briarproject.briar.test.BriarTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ADDED_CONTACT_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS;
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
import static org.hamcrest.Matchers.array;
import static org.hamcrest.Matchers.samePropertyValuesAs;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class IntroduceeManagerTest extends BriarTestCase {
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 AuthorFactory authorFactory;
private final ContactManager contactManager;
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 SessionId sessionId;
private final Message message1;
public IntroduceeManagerTest() {
context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
MessageSender messageSender = context.mock(MessageSender.class);
db = context.mock(DatabaseComponent.class);
cryptoComponent = context.mock(CryptoComponent.class);
clientHelper = context.mock(ClientHelper.class);
clock = context.mock(Clock.class);
introductionGroupFactory =
context.mock(IntroductionGroupFactory.class);
TransportPropertyManager transportPropertyManager =
context.mock(TransportPropertyManager.class);
authorFactory = context.mock(AuthorFactory.class);
contactManager = context.mock(ContactManager.class);
IdentityManager identityManager = context.mock(IdentityManager.class);
introduceeManager = new IntroduceeManager(messageSender, db,
clientHelper, clock, cryptoComponent, transportPropertyManager,
authorFactory, contactManager, identityManager,
introductionGroupFactory);
Author author0 = getAuthor();
AuthorId localAuthorId = new AuthorId(getRandomId());
ContactId contactId0 = new ContactId(234);
introducer =
new Contact(contactId0, author0, localAuthorId, true, true);
Author author1 = getAuthor();
AuthorId localAuthorId1 = new AuthorId(getRandomId());
ContactId contactId1 = new ContactId(234);
introducee1 =
new Contact(contactId1, author1, localAuthorId1, true, true);
Author author2 = getAuthor();
ContactId contactId2 = new ContactId(235);
introducee2 =
new Contact(contactId2, author2, localAuthorId, true, true);
localGroup1 = getGroup(CLIENT_ID);
introductionGroup1 = getGroup(CLIENT_ID);
sessionId = new SessionId(getRandomId());
localStateMessage = new Message(
new MessageId(getRandomId()),
localGroup1.getId(),
time,
getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
);
message1 = new Message(
new MessageId(getRandomId()),
introductionGroup1.getId(),
time,
getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
);
txn = new Transaction(null, false);
}
@Test
public void testIncomingRequestMessage()
throws DbException, FormatException {
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_REQUEST);
msg.put(GROUP_ID, introductionGroup1.getId());
msg.put(SESSION_ID, sessionId);
msg.put(MESSAGE_ID, message1.getId());
msg.put(MESSAGE_TIME, time);
msg.put(NAME, introducee2.getAuthor().getName());
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
BdfDictionary state =
initializeSessionState(txn, introductionGroup1.getId(), msg);
context.checking(new Expectations() {{
oneOf(clientHelper).mergeMessageMetadata(txn,
localStateMessage.getId(), state);
}});
introduceeManager.incomingMessage(txn, state, msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
@Test
public void testIncomingResponseMessage()
throws DbException, FormatException {
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_RESPONSE);
msg.put(GROUP_ID, introductionGroup1.getId());
msg.put(SESSION_ID, sessionId);
msg.put(MESSAGE_ID, message1.getId());
msg.put(MESSAGE_TIME, time);
msg.put(NAME, introducee2.getAuthor().getName());
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
BdfDictionary state =
initializeSessionState(txn, introductionGroup1.getId(), msg);
state.put(STATE, IntroduceeProtocolState.AWAIT_RESPONSES.ordinal());
// turn request message into a response
msg.put(ACCEPT, true);
msg.put(TIME, time);
msg.put(E_PUBLIC_KEY, getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES));
msg.put(TRANSPORT, new BdfDictionary());
context.checking(new Expectations() {{
oneOf(clientHelper).mergeMessageMetadata(txn,
localStateMessage.getId(), state);
}});
introduceeManager.incomingMessage(txn, state, msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
@Test
public void testDetectReplacedEphemeralPublicKey()
throws DbException, FormatException, GeneralSecurityException {
// TODO MR !237 should use its new default initialization method here
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_RESPONSE);
msg.put(GROUP_ID, introductionGroup1.getId());
msg.put(SESSION_ID, sessionId);
msg.put(MESSAGE_ID, message1.getId());
msg.put(MESSAGE_TIME, time);
msg.put(NAME, introducee2.getAuthor().getName());
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
BdfDictionary state =
initializeSessionState(txn, introductionGroup1.getId(), msg);
// prepare state for incoming ACK
state.put(STATE, IntroduceeProtocolState.AWAIT_ACK.ordinal());
state.put(ADDED_CONTACT_ID, 2);
byte[] nonce = getRandomBytes(42);
state.put(NONCE, nonce);
state.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
// create incoming ACK message
byte[] mac = getRandomBytes(MAC_LENGTH);
byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH);
BdfDictionary ack = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ACK),
new BdfEntry(SESSION_ID, sessionId),
new BdfEntry(GROUP_ID, introductionGroup1.getId()),
new BdfEntry(MAC, mac),
new BdfEntry(SIGNATURE, sig)
);
context.checking(new Expectations() {{
oneOf(cryptoComponent).verifySignature(sig, SIGNING_LABEL, nonce,
introducee2.getAuthor().getPublicKey());
will(returnValue(false));
}});
try {
introduceeManager.incomingMessage(txn, state, ack);
fail();
} catch (DbException e) {
// expected
assertTrue(e.getCause() instanceof GeneralSecurityException);
}
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
@Test
public void testSignatureVerification()
throws FormatException, DbException, GeneralSecurityException {
byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey();
byte[] nonce = getRandomBytes(MAC_LENGTH);
byte[] sig = getRandomBytes(MAC_LENGTH);
BdfDictionary state = new BdfDictionary();
state.put(PUBLIC_KEY, publicKeyBytes);
state.put(NONCE, nonce);
state.put(SIGNATURE, sig);
context.checking(new Expectations() {{
oneOf(cryptoComponent).verifySignature(sig, SIGNING_LABEL, nonce,
publicKeyBytes);
will(returnValue(true));
}});
introduceeManager.verifySignature(state);
context.assertIsSatisfied();
}
@Test
public void testMacVerification()
throws FormatException, DbException, GeneralSecurityException {
byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey();
BdfDictionary tp = BdfDictionary.of(new BdfEntry("fake", "fake"));
byte[] ePublicKeyBytes = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
byte[] mac = getRandomBytes(MAC_LENGTH);
SecretKey macKey = getSecretKey();
// move state to where it would be after an ACK arrived
BdfDictionary state = new BdfDictionary();
state.put(PUBLIC_KEY, publicKeyBytes);
state.put(TRANSPORT, tp);
state.put(TIME, time);
state.put(E_PUBLIC_KEY, ePublicKeyBytes);
state.put(MAC, mac);
state.put(MAC_KEY, macKey.getBytes());
byte[] signBytes = getRandomBytes(42);
context.checking(new Expectations() {{
oneOf(clientHelper).toByteArray(
BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time));
will(returnValue(signBytes));
//noinspection unchecked
oneOf(cryptoComponent).mac(with(MAC_LABEL),
with(samePropertyValuesAs(macKey)),
with(array(equal(signBytes))));
will(returnValue(mac));
}});
introduceeManager.verifyMac(state);
context.assertIsSatisfied();
// now produce wrong MAC
context.checking(new Expectations() {{
oneOf(clientHelper).toByteArray(
BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time));
will(returnValue(signBytes));
//noinspection unchecked
oneOf(cryptoComponent).mac(with(MAC_LABEL),
with(samePropertyValuesAs(macKey)),
with(array(equal(signBytes))));
will(returnValue(getRandomBytes(MAC_LENGTH)));
}});
try {
introduceeManager.verifyMac(state);
fail();
} catch (GeneralSecurityException e) {
// expected
}
context.assertIsSatisfied();
}
private BdfDictionary initializeSessionState(Transaction txn,
GroupId groupId, BdfDictionary msg)
throws DbException, FormatException {
SecureRandom secureRandom = context.mock(SecureRandom.class);
Bytes salt = new Bytes(new byte[64]);
BdfDictionary groupMetadata = BdfDictionary.of(
new BdfEntry(CONTACT, introducee1.getId().getInt())
);
boolean contactExists = false;
BdfDictionary state = new BdfDictionary();
state.put(STORAGE_ID, localStateMessage.getId());
state.put(STATE, AWAIT_REQUEST.getValue());
state.put(ROLE, ROLE_INTRODUCEE);
state.put(GROUP_ID, groupId);
state.put(INTRODUCER, introducer.getAuthor().getName());
state.put(CONTACT_ID_1, introducer.getId().getInt());
state.put(LOCAL_AUTHOR_ID, introducer.getLocalAuthorId().getBytes());
state.put(NOT_OUR_RESPONSE, localStateMessage.getId());
state.put(ANSWERED, false);
state.put(EXISTS, contactExists);
state.put(REMOTE_AUTHOR_ID, introducee2.getAuthor().getId());
state.put(REMOTE_AUTHOR_IS_US, false);
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(time));
oneOf(cryptoComponent).getSecureRandom();
will(returnValue(secureRandom));
oneOf(secureRandom).nextBytes(salt.getBytes());
oneOf(introductionGroupFactory).createLocalGroup();
will(returnValue(localGroup1));
oneOf(clientHelper)
.createMessage(localGroup1.getId(), time, BdfList.of(salt));
will(returnValue(localStateMessage));
// who is making the introduction? who is the introducer?
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
groupId);
will(returnValue(groupMetadata));
oneOf(db).getContact(txn, introducer.getId());
will(returnValue(introducer));
// create remote author to check if contact exists
oneOf(authorFactory).createAuthor(introducee2.getAuthor().getName(),
introducee2.getAuthor().getPublicKey());
will(returnValue(introducee2.getAuthor()));
oneOf(contactManager)
.contactExists(txn, introducee2.getAuthor().getId(),
introducer.getLocalAuthorId());
will(returnValue(contactExists));
// checks if remote author is one of our identities
oneOf(db).containsLocalAuthor(txn, introducee2.getAuthor().getId());
will(returnValue(false));
// store session state
oneOf(clientHelper)
.addLocalMessage(txn, localStateMessage, state, false);
}});
BdfDictionary result = introduceeManager.initialize(txn, groupId, msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
return result;
}
}

View File

@@ -1,179 +0,0 @@
package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.test.BriarTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;
import java.security.SecureRandom;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES;
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS;
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
import static org.junit.Assert.assertFalse;
public class IntroducerManagerTest extends BriarTestCase {
private final Mockery context;
private final IntroducerManager introducerManager;
private final CryptoComponent cryptoComponent;
private final ClientHelper clientHelper;
private final IntroductionGroupFactory introductionGroupFactory;
private final MessageSender messageSender;
private final Clock clock;
private final Contact introducee1;
private final Contact introducee2;
private final Group localGroup0;
private final Group introductionGroup1;
private final Group introductionGroup2;
public IntroducerManagerTest() {
context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
messageSender = context.mock(MessageSender.class);
cryptoComponent = context.mock(CryptoComponent.class);
clientHelper = context.mock(ClientHelper.class);
clock = context.mock(Clock.class);
introductionGroupFactory =
context.mock(IntroductionGroupFactory.class);
introducerManager =
new IntroducerManager(messageSender, clientHelper, clock,
cryptoComponent, introductionGroupFactory);
Author author1 = getAuthor();
AuthorId localAuthorId1 = new AuthorId(getRandomId());
ContactId contactId1 = new ContactId(234);
introducee1 =
new Contact(contactId1, author1, localAuthorId1, true, true);
Author author2 = getAuthor();
AuthorId localAuthorId2 = new AuthorId(getRandomId());
ContactId contactId2 = new ContactId(235);
introducee2 =
new Contact(contactId2, author2, localAuthorId2, true, true);
localGroup0 = getGroup(CLIENT_ID);
introductionGroup1 = getGroup(CLIENT_ID);
introductionGroup2 = getGroup(CLIENT_ID);
context.assertIsSatisfied();
}
@Test
public void testMakeIntroduction() throws DbException, FormatException {
Transaction txn = new Transaction(null, false);
long time = 42L;
context.setImposteriser(ClassImposteriser.INSTANCE);
SecureRandom secureRandom = context.mock(SecureRandom.class);
Bytes salt = new Bytes(new byte[64]);
Message msg = new Message(new MessageId(getRandomId()),
localGroup0.getId(), time, getRandomBytes(64));
BdfDictionary state = new BdfDictionary();
state.put(SESSION_ID, msg.getId());
state.put(STORAGE_ID, msg.getId());
state.put(STATE, PREPARE_REQUESTS.getValue());
state.put(ROLE, ROLE_INTRODUCER);
state.put(GROUP_ID_1, introductionGroup1.getId());
state.put(GROUP_ID_2, introductionGroup2.getId());
state.put(CONTACT_1, introducee1.getAuthor().getName());
state.put(CONTACT_2, introducee2.getAuthor().getName());
state.put(CONTACT_ID_1, introducee1.getId().getInt());
state.put(CONTACT_ID_2, introducee2.getId().getInt());
state.put(AUTHOR_ID_1, introducee1.getAuthor().getId());
state.put(AUTHOR_ID_2, introducee2.getAuthor().getId());
BdfDictionary state2 = (BdfDictionary) state.clone();
state2.put(STATE, AWAIT_RESPONSES.getValue());
BdfDictionary msg1 = new BdfDictionary();
msg1.put(TYPE, TYPE_REQUEST);
msg1.put(SESSION_ID, state.getRaw(SESSION_ID));
msg1.put(GROUP_ID, state.getRaw(GROUP_ID_1));
msg1.put(NAME, state.getString(CONTACT_2));
msg1.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
BdfDictionary msg1send = (BdfDictionary) msg1.clone();
msg1send.put(MESSAGE_TIME, time);
BdfDictionary msg2 = new BdfDictionary();
msg2.put(TYPE, TYPE_REQUEST);
msg2.put(SESSION_ID, state.getRaw(SESSION_ID));
msg2.put(GROUP_ID, state.getRaw(GROUP_ID_2));
msg2.put(NAME, state.getString(CONTACT_1));
msg2.put(PUBLIC_KEY, introducee1.getAuthor().getPublicKey());
BdfDictionary msg2send = (BdfDictionary) msg2.clone();
msg2send.put(MESSAGE_TIME, time);
context.checking(new Expectations() {{
// initialize and store session state
oneOf(clock).currentTimeMillis();
will(returnValue(time));
oneOf(cryptoComponent).getSecureRandom();
will(returnValue(secureRandom));
oneOf(secureRandom).nextBytes(salt.getBytes());
oneOf(introductionGroupFactory).createLocalGroup();
will(returnValue(localGroup0));
oneOf(clientHelper).createMessage(localGroup0.getId(), time,
BdfList.of(salt));
will(returnValue(msg));
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee2);
will(returnValue(introductionGroup2));
oneOf(clientHelper).addLocalMessage(txn, msg, state, false);
// send message
oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), state2);
oneOf(messageSender).sendMessage(txn, msg1send);
oneOf(messageSender).sendMessage(txn, msg2send);
}});
introducerManager
.makeIntroduction(txn, introducee1, introducee2, null, time);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
@@ -11,7 +11,7 @@ import org.junit.Test;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID; import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
public class IntroductionCryptoTest extends BrambleMockTestCase { public class IntroductionCryptoTest extends BrambleMockTestCase {

View File

@@ -6,23 +6,21 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry; import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.TestDatabaseModule; import org.briarproject.bramble.test.TestDatabaseModule;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
@@ -38,56 +36,35 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID; import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.test.TestUtils.getTransportProperties;
import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_MAC_KEY_LABEL; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_NONCE_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SHARED_SECRET_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION; import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_1;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCEE_2;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID;
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount; import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class IntroductionIntegrationTest public class IntroductionIntegrationTest
extends BriarIntegrationTest<IntroductionIntegrationTestComponent> { extends BriarIntegrationTest<IntroductionIntegrationTestComponent> {
@Inject
IntroductionGroupFactory introductionGroupFactory;
// objects accessed from background threads need to be volatile // objects accessed from background threads need to be volatile
private volatile IntroductionManager introductionManager0; private volatile IntroductionManager introductionManager0;
private volatile IntroductionManager introductionManager1; private volatile IntroductionManager introductionManager1;
@@ -102,7 +79,7 @@ public class IntroductionIntegrationTest
Logger.getLogger(IntroductionIntegrationTest.class.getName()); Logger.getLogger(IntroductionIntegrationTest.class.getName());
interface StateVisitor { interface StateVisitor {
boolean visit(BdfDictionary response); AcceptMessage visit(AcceptMessage response);
} }
@Before @Before
@@ -151,50 +128,50 @@ public class IntroductionIntegrationTest
.makeIntroduction(introducee1, introducee2, "Hi!", time); .makeIntroduction(introducee1, introducee2, "Hi!", time);
// check that messages are tracked properly // check that messages are tracked properly
Group g1 = introductionGroupFactory Group g1 = introductionManager0.getContactGroup(introducee1);
.createIntroductionGroup(introducee1); Group g2 = introductionManager0.getContactGroup(introducee2);
Group g2 = introductionGroupFactory assertGroupCount(messageTracker0, g1.getId(), 1, 0);
.createIntroductionGroup(introducee2); assertGroupCount(messageTracker0, g2.getId(), 1, 0);
assertGroupCount(messageTracker0, g1.getId(), 1, 0, time);
assertGroupCount(messageTracker0, g2.getId(), 1, 0, time);
// sync first request message // sync first REQUEST message
sync0To1(1, true); sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived); assertTrue(listener1.requestReceived);
assertGroupCount(messageTracker1, g1.getId(), 2, 1); assertGroupCount(messageTracker1, g1.getId(), 2, 1);
// sync second request message // sync second REQUEST message
sync0To2(1, true); sync0To2(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
assertTrue(listener2.requestReceived); assertTrue(listener2.requestReceived);
assertGroupCount(messageTracker2, g2.getId(), 2, 1); assertGroupCount(messageTracker2, g2.getId(), 2, 1);
// sync first response // sync first ACCEPT message
sync1To0(1, true); sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response1Received); assertTrue(listener0.response1Received);
assertGroupCount(messageTracker0, g1.getId(), 2, 1); assertGroupCount(messageTracker0, g1.getId(), 2, 1);
// sync second response // sync second ACCEPT message
sync2To0(1, true); sync2To0(1, true);
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response2Received); assertTrue(listener0.response2Received);
assertGroupCount(messageTracker0, g2.getId(), 2, 1); assertGroupCount(messageTracker0, g2.getId(), 2, 1);
// sync forwarded responses to introducees // sync forwarded ACCEPT messages to introducees
sync0To1(1, true); sync0To1(1, true);
sync0To2(1, true); sync0To2(1, true);
assertGroupCount(messageTracker1, g1.getId(), 2, 1);
assertGroupCount(messageTracker2, g2.getId(), 2, 1);
// sync first ACK and its forward // sync first AUTH and its forward
sync1To0(1, true); sync1To0(1, true);
sync0To2(1, true); sync0To2(1, true);
// sync second ACK and its forward // sync second AUTH and its forward as well as the following ACTIVATE
sync2To0(1, true); sync2To0(2, true);
sync0To1(1, true); sync0To1(2, true);
// sync first ACTIVATE and its forward
sync1To0(1, true);
sync0To2(1, true);
// wait for introduction to succeed // wait for introduction to succeed
eventWaiter.await(TIMEOUT, 2); eventWaiter.await(TIMEOUT, 2);
@@ -269,10 +246,8 @@ public class IntroductionIntegrationTest
assertFalse(contactManager2 assertFalse(contactManager2
.contactExists(author1.getId(), author2.getId())); .contactExists(author1.getId(), author2.getId()));
Group g1 = introductionGroupFactory Group g1 = introductionManager0.getContactGroup(introducee1);
.createIntroductionGroup(introducee1); Group g2 = introductionManager0.getContactGroup(introducee2);
Group g2 = introductionGroupFactory
.createIntroductionGroup(introducee2);
assertEquals(2, assertEquals(2,
introductionManager0.getIntroductionMessages(contactId1From0) introductionManager0.getIntroductionMessages(contactId1From0)
.size()); .size());
@@ -290,6 +265,10 @@ public class IntroductionIntegrationTest
introductionManager2.getIntroductionMessages(contactId0From2) introductionManager2.getIntroductionMessages(contactId0From2)
.size()); .size());
assertGroupCount(messageTracker2, g2.getId(), 3, 2); assertGroupCount(messageTracker2, g2.getId(), 3, 2);
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
} }
@Test @Test
@@ -342,6 +321,9 @@ public class IntroductionIntegrationTest
assertEquals(2, assertEquals(2,
introductionManager2.getIntroductionMessages(contactId0From2) introductionManager2.getIntroductionMessages(contactId0From2)
.size()); .size());
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
} }
@Test @Test
@@ -393,6 +375,9 @@ public class IntroductionIntegrationTest
// since introducee2 was already in FINISHED state when // since introducee2 was already in FINISHED state when
// introducee1's response arrived, she ignores and deletes it // introducee1's response arrived, she ignores and deletes it
assertDefaultUiMessages(); assertDefaultUiMessages();
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
} }
@Test @Test
@@ -432,6 +417,8 @@ public class IntroductionIntegrationTest
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response2Received); assertTrue(listener0.response2Received);
assertFalse(listener0.aborted); assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
} }
@Test @Test
@@ -452,61 +439,11 @@ public class IntroductionIntegrationTest
// make really sure we don't have that request // make really sure we don't have that request
assertTrue(introductionManager1.getIntroductionMessages(contactId0From1) assertTrue(introductionManager1.getIntroductionMessages(contactId0From1)
.isEmpty()); .isEmpty());
}
@Test // The message was invalid, so no abort message was sent
public void testSessionIdReuse() throws Exception { assertFalse(listener0.aborted);
addListeners(true, true); assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
// make introduction
long time = clock.currentTimeMillis();
introductionManager0
.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
// sync first request message
sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// get SessionId
List<IntroductionMessage> list = new ArrayList<>(
introductionManager1.getIntroductionMessages(contactId0From1));
assertEquals(2, list.size());
assertTrue(list.get(0) instanceof IntroductionRequest);
IntroductionRequest msg = (IntroductionRequest) list.get(0);
SessionId sessionId = msg.getSessionId();
// get contact group
Group group =
introductionGroupFactory.createIntroductionGroup(contact1From0);
// create new message with same SessionId
BdfDictionary d = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_REQUEST),
new BdfEntry(SESSION_ID, sessionId),
new BdfEntry(GROUP_ID, group.getId()),
new BdfEntry(NAME, getRandomString(42)),
new BdfEntry(PUBLIC_KEY, getRandomBytes(MAX_PUBLIC_KEY_LENGTH))
);
// reset request received state
listener1.requestReceived = false;
// add the message to the queue
MessageSender sender0 = c0.getMessageSender();
Transaction txn = db0.startTransaction(false);
try {
sender0.sendMessage(txn, d);
db0.commitTransaction(txn);
} finally {
db0.endTransaction(txn);
}
// actually send message
sync0To1(1, false);
// make sure it does not arrive
assertFalse(listener1.requestReceived);
} }
@Test @Test
@@ -523,34 +460,20 @@ public class IntroductionIntegrationTest
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived); assertTrue(listener1.requestReceived);
// get database and local group for introducee // get local group for introducee1
Group group1 = introductionGroupFactory.createLocalGroup(); Group group1 =
contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
// get local session state messages
Map<MessageId, Metadata> map;
Transaction txn = db1.startTransaction(false);
try {
map = db1.getMessageMetadata(txn, group1.getId());
db1.commitTransaction(txn);
} finally {
db1.endTransaction(txn);
}
// check that we have one session state // check that we have one session state
assertEquals(1, map.size()); assertEquals(1, c1.getClientHelper()
.getMessageMetadataAsDictionary(group1.getId()).size());
// introducee1 removes introducer // introducee1 removes introducer
contactManager1.removeContact(contactId0From1); contactManager1.removeContact(contactId0From1);
// get local session state messages again
txn = db1.startTransaction(false);
try {
map = db1.getMessageMetadata(txn, group1.getId());
db1.commitTransaction(txn);
} finally {
db1.endTransaction(txn);
}
// make sure local state got deleted // make sure local state got deleted
assertEquals(0, map.size()); assertEquals(0, c1.getClientHelper()
.getMessageMetadataAsDictionary(group1.getId()).size());
} }
@Test @Test
@@ -567,48 +490,36 @@ public class IntroductionIntegrationTest
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived); assertTrue(listener1.requestReceived);
// get database and local group for introducee // get local group for introducer
Group group1 = introductionGroupFactory.createLocalGroup(); Group group0 =
contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
// get local session state messages
Map<MessageId, Metadata> map;
Transaction txn = db0.startTransaction(false);
try {
map = db0.getMessageMetadata(txn, group1.getId());
db0.commitTransaction(txn);
} finally {
db0.endTransaction(txn);
}
// check that we have one session state // check that we have one session state
assertEquals(1, map.size()); assertEquals(1, c0.getClientHelper()
.getMessageMetadataAsDictionary(group0.getId()).size());
// introducer removes introducee1 // introducer removes introducee1
contactManager0.removeContact(contactId1From0); contactManager0.removeContact(contactId1From0);
// get local session state messages again
txn = db0.startTransaction(false);
try {
map = db0.getMessageMetadata(txn, group1.getId());
db0.commitTransaction(txn);
} finally {
db0.endTransaction(txn);
}
// make sure local state is still there // make sure local state is still there
assertEquals(1, map.size()); assertEquals(1, c0.getClientHelper()
.getMessageMetadataAsDictionary(group0.getId()).size());
// ensure introducer has aborted the session
assertTrue(listener0.aborted);
// sync REQUEST and ABORT message
sync0To2(2, true);
// ensure introducee2 has aborted the session as well
assertTrue(listener2.aborted);
// introducer removes other introducee // introducer removes other introducee
contactManager0.removeContact(contactId2From0); contactManager0.removeContact(contactId2From0);
// get local session state messages again
txn = db0.startTransaction(false);
try {
map = db0.getMessageMetadata(txn, group1.getId());
db0.commitTransaction(txn);
} finally {
db0.endTransaction(txn);
}
// make sure local state is gone now // make sure local state is gone now
assertEquals(0, map.size()); assertEquals(0, c0.getClientHelper()
.getMessageMetadataAsDictionary(group0.getId()).size());
} }
private void testModifiedResponse(StateVisitor visitor) private void testModifiedResponse(StateVisitor visitor)
@@ -630,26 +541,36 @@ public class IntroductionIntegrationTest
eventWaiter.await(TIMEOUT, 1); eventWaiter.await(TIMEOUT, 1);
// get response to be forwarded // get response to be forwarded
ClientHelper ch = c0.getClientHelper(); // need 0's ClientHelper here AcceptMessage message =
Entry<MessageId, BdfDictionary> resp = (AcceptMessage) getMessageFor(c0.getClientHelper(),
getMessageFor(ch, contact2From0, TYPE_RESPONSE); contact2From0, ACCEPT);
MessageId responseId = resp.getKey();
BdfDictionary response = resp.getValue();
// adapt outgoing message queue to removed message
Group g2 = introductionGroupFactory
.createIntroductionGroup(contact2From0);
decreaseOutgoingMessageCounter(ch, g2.getId());
// allow visitor to modify response // allow visitor to modify response
boolean earlyAbort = visitor.visit(response); AcceptMessage m = visitor.visit(message);
// replace original response with modified one // replace original response with modified one
MessageSender sender0 = c0.getMessageSender();
Transaction txn = db0.startTransaction(false); Transaction txn = db0.startTransaction(false);
try { try {
db0.deleteMessage(txn, responseId); db0.removeMessage(txn, message.getMessageId());
sender0.sendMessage(txn, response); Message msg = c0.getMessageEncoder()
.encodeAcceptMessage(m.getGroupId(), m.getTimestamp(),
m.getPreviousMessageId(), m.getSessionId(),
m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
m.getTransportProperties());
c0.getClientHelper()
.addLocalMessage(txn, msg, new BdfDictionary(), true);
Group group0 = contactGroupFactory
.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
BdfDictionary query = BdfDictionary.of(
new BdfEntry(SESSION_KEY_SESSION_ID, m.getSessionId())
);
Map.Entry<MessageId, BdfDictionary> session = c0.getClientHelper()
.getMessageMetadataAsDictionary(txn, group0.getId(), query)
.entrySet().iterator().next();
replacePreviousLocalMessageId(contact2From0.getAuthor(),
session.getValue(), msg.getId());
c0.getClientHelper().mergeMessageMetadata(txn, session.getKey(),
session.getValue());
db0.commitTransaction(txn); db0.commitTransaction(txn);
} finally { } finally {
db0.endTransaction(txn); db0.endTransaction(txn);
@@ -663,21 +584,14 @@ public class IntroductionIntegrationTest
sync0To1(1, true); sync0To1(1, true);
sync0To2(1, true); sync0To2(1, true);
// sync first ACK and forward it // sync first AUTH and forward it
sync1To0(1, true); sync1To0(1, true);
sync0To2(1, true); sync0To2(1, true);
// introducee2 should have detected the fake now // introducee2 should have detected the fake now
// and deleted introducee1 again assertFalse(listener0.aborted);
Collection<Contact> contacts2; assertFalse(listener1.aborted);
txn = db2.startTransaction(true); assertTrue(listener2.aborted);
try {
contacts2 = db2.getContacts(txn);
db2.commitTransaction(txn);
} finally {
db2.endTransaction(txn);
}
assertEquals(1, contacts2.size());
// sync introducee2's ack and following abort // sync introducee2's ack and following abort
sync2To0(2, true); sync2To0(2, true);
@@ -687,144 +601,44 @@ public class IntroductionIntegrationTest
// sync abort messages to introducees // sync abort messages to introducees
sync0To1(2, true); sync0To1(2, true);
sync0To2(1, true);
if (earlyAbort) { // ensure everybody got the abort now
assertTrue(listener1.aborted); assertTrue(listener0.aborted);
assertTrue(listener2.aborted); assertTrue(listener1.aborted);
} else { assertTrue(listener2.aborted);
assertTrue(listener2.aborted);
// when aborted late, introducee1 keeps the contact,
// so introducer can not make contacts disappear by aborting
Collection<Contact> contacts1;
txn = db1.startTransaction(true);
try {
contacts1 = db1.getContacts(txn);
db1.commitTransaction(txn);
} finally {
db1.endTransaction(txn);
}
assertEquals(2, contacts1.size());
}
} }
@Test @Test
public void testModifiedTransportProperties() throws Exception { public void testModifiedTransportProperties() throws Exception {
testModifiedResponse(response -> { testModifiedResponse(
BdfDictionary tp = response.getDictionary(TRANSPORT, null); m -> new AcceptMessage(m.getMessageId(), m.getGroupId(),
tp.put("fakeId", BdfDictionary.of(new BdfEntry("fake", "fake"))); m.getTimestamp(), m.getPreviousMessageId(),
response.put(TRANSPORT, tp); m.getSessionId(), m.getEphemeralPublicKey(),
return false; m.getAcceptTimestamp(),
}); getTransportPropertiesMap(2))
);
} }
@Test @Test
public void testModifiedTimestamp() throws Exception { public void testModifiedTimestamp() throws Exception {
testModifiedResponse(response -> { testModifiedResponse(
long timestamp = response.getLong(TIME, 0L); m -> new AcceptMessage(m.getMessageId(), m.getGroupId(),
response.put(TIME, timestamp + 1); m.getTimestamp(), m.getPreviousMessageId(),
return false; m.getSessionId(), m.getEphemeralPublicKey(),
}); clock.currentTimeMillis(),
m.getTransportProperties())
);
} }
@Test @Test
public void testModifiedEphemeralPublicKey() throws Exception { public void testModifiedEphemeralPublicKey() throws Exception {
testModifiedResponse(response -> { testModifiedResponse(
KeyPair keyPair = crypto.generateAgreementKeyPair(); m -> new AcceptMessage(m.getMessageId(), m.getGroupId(),
response.put(E_PUBLIC_KEY, keyPair.getPublic().getEncoded()); m.getTimestamp(), m.getPreviousMessageId(),
return true; m.getSessionId(),
}); getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
} m.getAcceptTimestamp(), m.getTransportProperties())
);
@Test
public void testModifiedEphemeralPublicKeyWithFakeMac()
throws Exception {
// initialize a real introducee manager
MessageSender messageSender = c2.getMessageSender();
TransportPropertyManager tpManager = c2.getTransportPropertyManager();
IntroduceeManager manager2 =
new IntroduceeManager(messageSender, db2, clientHelper, clock,
crypto, tpManager, authorFactory, contactManager2,
identityManager2, introductionGroupFactory);
// create keys
KeyPair keyPair1 = crypto.generateSignatureKeyPair();
KeyPair eKeyPair1 = crypto.generateAgreementKeyPair();
KeyPair eKeyPair2 = crypto.generateAgreementKeyPair();
// Nonce 1
byte[][] inputs = {
new byte[] {CLIENT_VERSION},
eKeyPair1.getPublic().getEncoded(),
eKeyPair2.getPublic().getEncoded()
};
SecretKey sharedSecret = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
eKeyPair2.getPublic(), eKeyPair1, inputs);
byte[] nonce1 = crypto.mac(ALICE_NONCE_LABEL, sharedSecret);
// Signature 1
byte[] sig1 = crypto.sign(SIGNING_LABEL, nonce1,
keyPair1.getPrivate().getEncoded());
// MAC 1
SecretKey macKey1 = crypto.deriveKey(ALICE_MAC_KEY_LABEL, sharedSecret);
BdfDictionary tp1 = BdfDictionary.of(new BdfEntry("fake", "fake"));
long time1 = clock.currentTimeMillis();
BdfList toMacList = BdfList.of(keyPair1.getPublic().getEncoded(),
eKeyPair1.getPublic().getEncoded(), tp1, time1);
byte[] toMac = clientHelper.toByteArray(toMacList);
byte[] mac1 = crypto.mac(MAC_LABEL, macKey1, toMac);
// create only relevant part of state for introducee2
BdfDictionary state = new BdfDictionary();
state.put(PUBLIC_KEY, keyPair1.getPublic().getEncoded());
state.put(TRANSPORT, tp1);
state.put(TIME, time1);
state.put(E_PUBLIC_KEY, eKeyPair1.getPublic().getEncoded());
state.put(MAC, mac1);
state.put(MAC_KEY, macKey1.getBytes());
state.put(NONCE, nonce1);
state.put(SIGNATURE, sig1);
// MAC and signature verification should pass
manager2.verifyMac(state);
manager2.verifySignature(state);
// replace ephemeral key pair and recalculate matching keys and nonce
KeyPair eKeyPair1f = crypto.generateAgreementKeyPair();
byte[][] fakeInputs = {
new byte[] {CLIENT_VERSION},
eKeyPair1f.getPublic().getEncoded(),
eKeyPair2.getPublic().getEncoded()
};
sharedSecret = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
eKeyPair2.getPublic(), eKeyPair1f, fakeInputs);
nonce1 = crypto.mac(ALICE_NONCE_LABEL, sharedSecret);
// recalculate MAC
macKey1 = crypto.deriveKey(ALICE_MAC_KEY_LABEL, sharedSecret);
toMacList = BdfList.of(keyPair1.getPublic().getEncoded(),
eKeyPair1f.getPublic().getEncoded(), tp1, time1);
toMac = clientHelper.toByteArray(toMacList);
mac1 = crypto.mac(MAC_LABEL, macKey1, toMac);
// update state with faked information
state.put(E_PUBLIC_KEY, eKeyPair1f.getPublic().getEncoded());
state.put(MAC, mac1);
state.put(MAC_KEY, macKey1.getBytes());
state.put(NONCE, nonce1);
// MAC verification should still pass
manager2.verifyMac(state);
// Signature can not be verified, because we don't have private
// long-term key to fake it
try {
manager2.verifySignature(state);
fail();
} catch (GeneralSecurityException e) {
// expected
}
} }
private void addTransportProperties() private void addTransportProperties()
@@ -832,17 +646,15 @@ public class IntroductionIntegrationTest
TransportPropertyManager tpm0 = c0.getTransportPropertyManager(); TransportPropertyManager tpm0 = c0.getTransportPropertyManager();
TransportPropertyManager tpm1 = c1.getTransportPropertyManager(); TransportPropertyManager tpm1 = c1.getTransportPropertyManager();
TransportPropertyManager tpm2 = c2.getTransportPropertyManager(); TransportPropertyManager tpm2 = c2.getTransportPropertyManager();
TransportProperties tp = new TransportProperties(
Collections.singletonMap("key", "value"));
tpm0.mergeLocalProperties(TRANSPORT_ID, tp); tpm0.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
sync0To1(1, true); sync0To1(1, true);
sync0To2(1, true); sync0To2(1, true);
tpm1.mergeLocalProperties(TRANSPORT_ID, tp); tpm1.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
sync1To0(1, true); sync1To0(1, true);
tpm2.mergeLocalProperties(TRANSPORT_ID, tp); tpm2.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
sync2To0(1, true); sync2To0(1, true);
} }
@@ -935,7 +747,7 @@ public class IntroductionIntegrationTest
time); time);
} }
} }
} catch (DbException | FormatException exception) { } catch (DbException exception) {
eventWaiter.rethrow(exception); eventWaiter.rethrow(exception);
} finally { } finally {
eventWaiter.resume(); eventWaiter.resume();
@@ -945,7 +757,6 @@ public class IntroductionIntegrationTest
Contact contact = ((IntroductionSucceededEvent) e).getContact(); Contact contact = ((IntroductionSucceededEvent) e).getContact();
eventWaiter eventWaiter
.assertFalse(contact.getId().equals(contactId0From1)); .assertFalse(contact.getId().equals(contactId0From1));
eventWaiter.assertTrue(contact.isActive());
eventWaiter.resume(); eventWaiter.resume();
} else if (e instanceof IntroductionAbortedEvent) { } else if (e instanceof IntroductionAbortedEvent) {
aborted = true; aborted = true;
@@ -981,30 +792,41 @@ public class IntroductionIntegrationTest
} }
private void decreaseOutgoingMessageCounter(ClientHelper ch, GroupId g) private void replacePreviousLocalMessageId(Author author,
throws FormatException, DbException { BdfDictionary d, MessageId id) throws FormatException {
BdfDictionary gD = ch.getGroupMetadataAsDictionary(g); BdfDictionary i1 = d.getDictionary(SESSION_KEY_INTRODUCEE_1);
LOG.warning(gD.toString()); BdfDictionary i2 = d.getDictionary(SESSION_KEY_INTRODUCEE_2);
BdfDictionary queue = gD.getDictionary(QUEUE_STATE_KEY); Author a1 = clientHelper
queue.put("nextOut", queue.getLong("nextOut") - 1); .parseAndValidateAuthor(i1.getList(SESSION_KEY_AUTHOR));
gD.put(QUEUE_STATE_KEY, queue); Author a2 = clientHelper
ch.mergeGroupMetadata(g, gD); .parseAndValidateAuthor(i2.getList(SESSION_KEY_AUTHOR));
if (a1.equals(author)) {
i1.put(SESSION_KEY_LAST_LOCAL_MESSAGE_ID, id);
d.put(SESSION_KEY_INTRODUCEE_1, i1);
} else if (a2.equals(author)) {
i2.put(SESSION_KEY_LAST_LOCAL_MESSAGE_ID, id);
d.put(SESSION_KEY_INTRODUCEE_2, i2);
} else {
throw new AssertionError();
}
} }
private Entry<MessageId, BdfDictionary> getMessageFor(ClientHelper ch, private AbstractIntroductionMessage getMessageFor(ClientHelper ch,
Contact contact, long type) throws FormatException, DbException { Contact contact, MessageType type)
Entry<MessageId, BdfDictionary> response = null; throws FormatException, DbException {
Group g = introductionGroupFactory Group g = introductionManager0.getContactGroup(contact);
.createIntroductionGroup(contact); BdfDictionary query = BdfDictionary.of(
new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue())
);
Map<MessageId, BdfDictionary> map = Map<MessageId, BdfDictionary> map =
ch.getMessageMetadataAsDictionary(g.getId()); ch.getMessageMetadataAsDictionary(g.getId(), query);
for (Entry<MessageId, BdfDictionary> entry : map.entrySet()) { assertEquals(1, map.size());
if (entry.getValue().getLong(TYPE) == type) { MessageId id = map.entrySet().iterator().next().getKey();
response = entry; Message m = ch.getMessage(id);
} BdfList body = ch.getMessageAsList(id);
} //noinspection ConstantConditions
assertTrue(response != null); return c0.getMessageParser().parseAcceptMessage(m, body);
return response;
} }
} }

View File

@@ -59,6 +59,7 @@ interface IntroductionIntegrationTestComponent
void inject(IntroductionIntegrationTest init); void inject(IntroductionIntegrationTest init);
MessageSender getMessageSender(); MessageEncoder getMessageEncoder();
MessageParser getMessageParser();
} }

View File

@@ -1,291 +0,0 @@
package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.test.BriarTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
import static org.junit.Assert.assertFalse;
public class IntroductionManagerImplTest extends BriarTestCase {
private final Mockery context;
private final IntroductionManagerImpl introductionManager;
private final IntroducerManager introducerManager;
private final IntroduceeManager introduceeManager;
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final MessageTracker messageTracker;
private final IntroductionGroupFactory introductionGroupFactory;
private final SessionId sessionId = new SessionId(getRandomId());
private final MessageId storageId = new MessageId(sessionId.getBytes());
private final long time = 42L;
private final Contact introducee1;
private final Contact introducee2;
private final Group introductionGroup1;
private final Group introductionGroup2;
private final Message message1;
private Transaction txn;
public IntroductionManagerImplTest() {
Author author1 = getAuthor();
AuthorId localAuthorId1 = new AuthorId(getRandomId());
ContactId contactId1 = new ContactId(234);
introducee1 =
new Contact(contactId1, author1, localAuthorId1, true, true);
Author author2 = getAuthor();
AuthorId localAuthorId2 = new AuthorId(getRandomId());
ContactId contactId2 = new ContactId(235);
introducee2 =
new Contact(contactId2, author2, localAuthorId2, true, true);
introductionGroup1 = getGroup(CLIENT_ID);
introductionGroup2 = getGroup(CLIENT_ID);
message1 = new Message(
new MessageId(getRandomId()),
introductionGroup1.getId(),
time,
getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
);
// mock ALL THE THINGS!!!
context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
introducerManager = context.mock(IntroducerManager.class);
introduceeManager = context.mock(IntroduceeManager.class);
db = context.mock(DatabaseComponent.class);
clientHelper = context.mock(ClientHelper.class);
MetadataParser metadataParser = context.mock(MetadataParser.class);
messageTracker = context.mock(MessageTracker.class);
introductionGroupFactory = context.mock(IntroductionGroupFactory.class);
introductionManager = new IntroductionManagerImpl(db, clientHelper,
metadataParser, messageTracker, introducerManager,
introduceeManager, introductionGroupFactory);
}
@Test
public void testMakeIntroduction() throws DbException, FormatException {
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(introducerManager)
.makeIntroduction(txn, introducee1, introducee2, null,
time);
// get both introduction groups
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(introductionGroupFactory)
.createIntroductionGroup(introducee2);
will(returnValue(introductionGroup2));
// track message for group 1
oneOf(messageTracker).trackMessage(txn,
introductionGroup1.getId(), time, true);
// track message for group 2
oneOf(messageTracker).trackMessage(txn,
introductionGroup2.getId(), time, true);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
introductionManager
.makeIntroduction(introducee1, introducee2, null, time);
context.assertIsSatisfied();
}
@Test
public void testAcceptIntroduction() throws DbException, FormatException {
BdfDictionary state = BdfDictionary.of(
new BdfEntry(GROUP_ID_1, introductionGroup1.getId()),
new BdfEntry(GROUP_ID_2, introductionGroup2.getId())
);
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, introducee1.getId());
will(returnValue(introducee1));
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
will(returnValue(state));
oneOf(introduceeManager).acceptIntroduction(txn, state, time);
// track message
oneOf(messageTracker).trackMessage(txn,
introductionGroup1.getId(), time, true);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
introductionManager
.acceptIntroduction(introducee1.getId(), sessionId, time);
context.assertIsSatisfied();
}
@Test
public void testDeclineIntroduction() throws DbException, FormatException {
BdfDictionary state = BdfDictionary.of(
new BdfEntry(GROUP_ID_1, introductionGroup1.getId()),
new BdfEntry(GROUP_ID_2, introductionGroup2.getId())
);
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, introducee1.getId());
will(returnValue(introducee1));
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
will(returnValue(state));
oneOf(introduceeManager).declineIntroduction(txn, state, time);
// track message
oneOf(messageTracker).trackMessage(txn,
introductionGroup1.getId(), time, true);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
introductionManager
.declineIntroduction(introducee1.getId(), sessionId, time);
context.assertIsSatisfied();
}
@Test
public void testGetIntroductionMessages()
throws DbException, FormatException {
Map<MessageId, BdfDictionary> metadata = Collections.emptyMap();
Collection<MessageStatus> statuses = Collections.emptyList();
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContact(txn, introducee1.getId());
will(returnValue(introducee1));
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
will(returnValue(introductionGroup1));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
introductionGroup1.getId());
will(returnValue(metadata));
oneOf(db).getMessageStatus(txn, introducee1.getId(),
introductionGroup1.getId());
will(returnValue(statuses));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
introductionManager.getIntroductionMessages(introducee1.getId());
context.assertIsSatisfied();
}
@Test
public void testIncomingRequestMessage()
throws DbException, FormatException {
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_REQUEST);
BdfDictionary state = new BdfDictionary();
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(introduceeManager)
.initialize(txn, introductionGroup1.getId(), msg);
will(returnValue(state));
oneOf(introduceeManager)
.incomingMessage(txn, state, msg);
// track message
oneOf(messageTracker).trackIncomingMessage(txn, message1);
}});
introductionManager
.incomingMessage(txn, message1, new BdfList(), msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
@Test
public void testIncomingResponseMessage()
throws DbException, FormatException {
BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_RESPONSE),
new BdfEntry(SESSION_ID, sessionId)
);
BdfDictionary state = new BdfDictionary();
state.put(ROLE, ROLE_INTRODUCER);
state.put(GROUP_ID_1, introductionGroup1.getId());
state.put(GROUP_ID_2, introductionGroup2.getId());
txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
will(returnValue(state));
oneOf(introducerManager).incomingMessage(txn, state, msg);
// track message
oneOf(messageTracker).trackIncomingMessage(txn, message1);
}});
introductionManager
.incomingMessage(txn, message1, new BdfList(), msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
}

View File

@@ -1,361 +1,424 @@
package org.briarproject.briar.introduction; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry; import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.test.ValidatorTestCase;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.test.BriarTestCase; import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test; import org.junit.Test;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import javax.annotation.Nullable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getClientId;
import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY; import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH; import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; import static org.briarproject.briar.introduction.MessageType.DECLINE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG; import static org.briarproject.briar.introduction.MessageType.REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ABORT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class IntroductionValidatorTest extends BriarTestCase { public class IntroductionValidatorTest extends ValidatorTestCase {
private final Mockery context = new Mockery(); private final MessageEncoder messageEncoder =
private final Group group; context.mock(MessageEncoder.class);
private final Message message; private final IntroductionValidator validator =
private final IntroductionValidator validator; new IntroductionValidator(messageEncoder, clientHelper,
private final Clock clock = new SystemClock(); metadataEncoder, clock);
public IntroductionValidatorTest() { private final SessionId sessionId = new SessionId(getRandomId());
group = getGroup(getClientId()); private final MessageId previousMsgId = new MessageId(getRandomId());
MessageId messageId = new MessageId(getRandomId()); private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH);
long timestamp = System.currentTimeMillis(); private final BdfDictionary meta = new BdfDictionary();
byte[] raw = getRandomBytes(123); private final long acceptTimestamp = 42;
message = new Message(messageId, group.getId(), timestamp, raw); private final BdfDictionary transportProperties = BdfDictionary.of(
new BdfEntry("transportId", new BdfDictionary())
);
ClientHelper clientHelper = context.mock(ClientHelper.class); private final byte[] mac = getRandomBytes(MAC_BYTES);
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class); private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES);
validator = new IntroductionValidator(clientHelper, metadataEncoder,
clock);
context.assertIsSatisfied();
}
// //
// Introduction Requests // Introduction REQUEST
// //
@Test @Test
public void testValidateProperIntroductionRequest() throws Exception { public void testAcceptsRequest() throws Exception {
byte[] sessionId = getRandomId(); BdfList body = BdfList.of(REQUEST.getValue(), previousMsgId.getBytes(),
String name = getRandomString(MAX_AUTHOR_NAME_LENGTH); authorList, text);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
String text = getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
BdfList body = BdfList.of(TYPE_REQUEST, sessionId, expectParseAuthor(authorList, author);
name, publicKey, text); expectEncodeRequestMetadata();
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
BdfDictionary result = assertExpectedContext(messageContext, previousMsgId);
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
assertEquals(name, result.getString(NAME));
assertEquals(publicKey, result.getRaw(PUBLIC_KEY));
assertEquals(text, result.getString(MSG));
context.assertIsSatisfied();
}
@Test(expected = FormatException.class)
public void testValidateIntroductionRequestWithNoName() throws Exception {
BdfDictionary msg = getValidIntroductionRequest();
// no NAME is message
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getRaw(PUBLIC_KEY));
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateIntroductionRequestWithLongName() throws Exception {
// too long NAME in message
BdfDictionary msg = getValidIntroductionRequest();
msg.put(NAME, msg.get(NAME) + "x");
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getString(NAME), msg.getRaw(PUBLIC_KEY));
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateIntroductionRequestWithWrongType()
throws Exception {
// wrong message type
BdfDictionary msg = getValidIntroductionRequest();
msg.put(TYPE, 324234);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getString(NAME), msg.getRaw(PUBLIC_KEY));
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
validator.validateMessage(message, group, body);
}
private BdfDictionary getValidIntroductionRequest() throws Exception {
byte[] sessionId = getRandomId();
Author author = getAuthor();
String text = getRandomString(MAX_MESSAGE_BODY_LENGTH);
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_REQUEST);
msg.put(SESSION_ID, sessionId);
msg.put(NAME, author.getName());
msg.put(PUBLIC_KEY, author.getPublicKey());
msg.put(MSG, text);
return msg;
}
//
// Introduction Responses
//
@Test
public void testValidateIntroductionAcceptResponse() throws Exception {
byte[] groupId = getRandomId();
byte[] sessionId = getRandomId();
long time = clock.currentTimeMillis();
byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
String transportId =
getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
BdfDictionary tProps = BdfDictionary.of(
new BdfEntry(getRandomString(MAX_PROPERTY_LENGTH),
getRandomString(MAX_PROPERTY_LENGTH))
);
BdfDictionary tp = BdfDictionary.of(
new BdfEntry(transportId, tProps)
);
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_RESPONSE);
msg.put(GROUP_ID, groupId);
msg.put(SESSION_ID, sessionId);
msg.put(ACCEPT, true);
msg.put(TIME, time);
msg.put(E_PUBLIC_KEY, publicKey);
msg.put(TRANSPORT, tp);
BdfList body = BdfList.of(TYPE_RESPONSE, msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT), msg.getLong(TIME),
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
BdfDictionary result =
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
assertEquals(true, result.getBoolean(ACCEPT));
assertEquals(publicKey, result.getRaw(E_PUBLIC_KEY));
assertEquals(tp, result.getDictionary(TRANSPORT));
context.assertIsSatisfied();
} }
@Test @Test
public void testValidateIntroductionDeclineResponse() throws Exception { public void testAcceptsRequestWithPreviousMsgIdNull() throws Exception {
BdfDictionary msg = getValidIntroductionResponse(false); BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, text);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT));
BdfDictionary result = validator.validateMessage(message, group, body) expectParseAuthor(authorList, author);
.getDictionary(); expectEncodeRequestMetadata();
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertFalse(result.getBoolean(ACCEPT)); assertExpectedContext(messageContext, null);
context.assertIsSatisfied(); }
@Test
public void testAcceptsRequestWithMessageNull() throws Exception {
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, null);
expectParseAuthor(authorList, author);
expectEncodeRequestMetadata();
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, null);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testValidateIntroductionResponseWithoutAccept() public void testRejectsTooShortBodyForRequest() throws Exception {
throws Exception { BdfList body = BdfList.of(REQUEST.getValue(), null, authorList);
BdfDictionary msg = getValidIntroductionResponse(false);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
validator.validateMessage(message, group, body); validator.validateMessage(message, group, body);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testValidateIntroductionResponseWithBrokenTp() public void testRejectsTooLongBodyForRequest() throws Exception {
throws Exception { BdfList body =
BdfDictionary msg = getValidIntroductionResponse(true); BdfList.of(REQUEST.getValue(), null, authorList, text, null);
BdfDictionary tp = msg.getDictionary(TRANSPORT);
tp.put(
getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH), "X");
msg.put(TRANSPORT, tp);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT), msg.getLong(TIME),
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
validator.validateMessage(message, group, body); validator.validateMessage(message, group, body);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testValidateIntroductionResponseWithoutPublicKey() public void testRejectsRawMessageForRequest() throws Exception {
throws Exception { BdfList body =
BdfDictionary msg = getValidIntroductionResponse(true); BdfList.of(REQUEST.getValue(), null, authorList, getRandomId());
expectParseAuthor(authorList, author);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT), msg.getLong(TIME),
msg.getDictionary(TRANSPORT));
validator.validateMessage(message, group, body); validator.validateMessage(message, group, body);
} }
private BdfDictionary getValidIntroductionResponse(boolean accept) @Test(expected = FormatException.class)
public void testRejectsStringMessageIdForRequest() throws Exception {
BdfList body =
BdfList.of(REQUEST.getValue(), "NoMessageId", authorList, null);
validator.validateMessage(message, group, body);
}
//
// Introduction ACCEPT
//
@Test
public void testAcceptsAccept() throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
acceptTimestamp, transportProperties);
context.checking(new Expectations() {{
oneOf(clientHelper).parseAndValidateTransportProperties(
transportProperties.getDictionary("transportId"));
}});
expectEncodeMetadata(ACCEPT);
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForAccept() throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForAccept() throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
acceptTimestamp, transportProperties, null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidSessionIdForAccept() throws Exception {
BdfList body =
BdfList.of(ACCEPT.getValue(), null, previousMsgId.getBytes(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp,
transportProperties);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidPreviousMsgIdForAccept() throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(), 1,
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp,
transportProperties);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongPublicKeyForAccept() throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp,
transportProperties);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsEmptyTransportPropertiesForAccept()
throws Exception { throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp,
new BdfDictionary());
validator.validateMessage(message, group, body);
}
byte[] groupId = getRandomId(); //
byte[] sessionId = getRandomId(); // Introduction DECLINE
long time = clock.currentTimeMillis(); //
byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
String transportId =
getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
BdfDictionary tProps = BdfDictionary.of(
new BdfEntry(getRandomString(MAX_PROPERTY_LENGTH),
getRandomString(MAX_PROPERTY_LENGTH))
);
BdfDictionary tp = BdfDictionary.of(
new BdfEntry(transportId, tProps)
);
BdfDictionary msg = new BdfDictionary(); @Test
msg.put(TYPE, TYPE_RESPONSE); public void testAcceptsDecline() throws Exception {
msg.put(GROUP_ID, groupId); BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(),
msg.put(SESSION_ID, sessionId); previousMsgId.getBytes());
msg.put(ACCEPT, accept);
if (accept) { expectEncodeMetadata(DECLINE);
msg.put(TIME, time); BdfMessageContext messageContext =
msg.put(E_PUBLIC_KEY, publicKey); validator.validateMessage(message, group, body);
msg.put(TRANSPORT, tp);
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForDecline() throws Exception {
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForDecline() throws Exception {
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidSessionIdForDecline() throws Exception {
BdfList body =
BdfList.of(DECLINE.getValue(), null, previousMsgId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidPreviousMsgIdForDecline() throws Exception {
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(), 1);
validator.validateMessage(message, group, body);
}
//
// Introduction AUTH
//
@Test
public void testAcceptsAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac, signature);
expectEncodeMetadata(AUTH);
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac, signature, null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortMacForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), getRandomBytes(MAC_BYTES - 1),
signature);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongMacForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(),
getRandomBytes(MAC_BYTES + 1), signature);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidMacForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), null, signature);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortSignatureForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac, getRandomBytes(0));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongSignatureForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac,
getRandomBytes(MAX_SIGNATURE_BYTES + 1));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidSignatureForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac, null);
validator.validateMessage(message, group, body);
}
//
// Introduction ACTIVATE
//
@Test
public void testAcceptsActivate() throws Exception {
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
previousMsgId.getBytes());
expectEncodeMetadata(ACTIVATE);
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForActivate() throws Exception {
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForActivate() throws Exception {
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidSessionIdForActivate() throws Exception {
BdfList body =
BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidPreviousMsgIdForActivate() throws Exception {
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), 1);
validator.validateMessage(message, group, body);
}
//
// Introduction ABORT
//
@Test
public void testAcceptsAbort() throws Exception {
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes());
expectEncodeMetadata(ABORT);
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForAbort() throws Exception {
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForAbort() throws Exception {
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidSessionIdForAbort() throws Exception {
BdfList body =
BdfList.of(ABORT.getValue(), null, previousMsgId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidPreviousMsgIdForAbort() throws Exception {
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(), 1);
validator.validateMessage(message, group, body);
}
//
// Introduction Helper Methods
//
private void expectEncodeRequestMetadata() {
context.checking(new Expectations() {{
oneOf(messageEncoder)
.encodeRequestMetadata(message.getTimestamp(), false, false,
false, false);
will(returnValue(meta));
}});
}
private void expectEncodeMetadata(MessageType type) {
context.checking(new Expectations() {{
oneOf(messageEncoder)
.encodeMetadata(type, sessionId, message.getTimestamp(),
false, false, false);
will(returnValue(meta));
}});
}
private void assertExpectedContext(BdfMessageContext c,
@Nullable MessageId dependency) {
assertEquals(meta, c.getDictionary());
if (dependency == null) {
assertEquals(0, c.getDependencies().size());
} else {
assertEquals(dependency, c.getDependencies().iterator().next());
} }
return msg;
}
//
// Introduction ACK
//
@Test
public void testValidateProperIntroductionAck() throws Exception {
byte[] sessionId = getRandomId();
byte[] mac = getRandomBytes(MAC_LENGTH);
byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH);
BdfList body = BdfList.of(TYPE_ACK, sessionId, mac, sig);
BdfDictionary result =
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE));
assertArrayEquals(sessionId, result.getRaw(SESSION_ID));
assertArrayEquals(mac, result.getRaw(MAC));
assertArrayEquals(sig, result.getRaw(SIGNATURE));
context.assertIsSatisfied();
}
@Test(expected = FormatException.class)
public void testValidateTooLongIntroductionAck() throws Exception {
BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ACK),
new BdfEntry(SESSION_ID, getRandomId()),
new BdfEntry("garbage", getRandomString(255))
);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getString("garbage"));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateIntroductionAckWithLongSessionId()
throws Exception {
BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ACK),
new BdfEntry(SESSION_ID, new byte[SessionId.LENGTH + 1])
);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
validator.validateMessage(message, group, body);
}
//
// Introduction Abort
//
@Test
public void testValidateProperIntroductionAbort() throws Exception {
byte[] sessionId = getRandomId();
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_ABORT);
msg.put(SESSION_ID, sessionId);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
BdfDictionary result =
validator.validateMessage(message, group, body).getDictionary();
assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE));
assertEquals(sessionId, result.getRaw(SESSION_ID));
context.assertIsSatisfied();
}
@Test(expected = FormatException.class)
public void testValidateTooLongIntroductionAbort() throws Exception {
BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ABORT),
new BdfEntry(SESSION_ID, getRandomId()),
new BdfEntry("garbage", getRandomString(255))
);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getString("garbage"));
validator.validateMessage(message, group, body);
} }
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
@@ -28,9 +28,9 @@ import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
import static org.briarproject.briar.introduction2.MessageType.ABORT; import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.introduction2.MessageType.REQUEST; import static org.briarproject.briar.introduction.MessageType.REQUEST;
import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor; import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -55,8 +55,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
private final SessionId sessionId = new SessionId(getRandomId()); private final SessionId sessionId = new SessionId(getRandomId());
private final MessageId previousMsgId = new MessageId(getRandomId()); private final MessageId previousMsgId = new MessageId(getRandomId());
private final Author author; private final Author author;
private final String text = private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH);
getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
private final byte[] ephemeralPublicKey = private final byte[] ephemeralPublicKey =
getRandomBytes(MAX_PUBLIC_KEY_LENGTH); getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
private final byte[] mac = getRandomBytes(MAC_BYTES); private final byte[] mac = getRandomBytes(MAC_BYTES);

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
@@ -16,8 +16,8 @@ import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
import static org.briarproject.briar.introduction2.MessageType.REQUEST; import static org.briarproject.briar.introduction.MessageType.REQUEST;
public class MessageEncoderTest extends BrambleMockTestCase { public class MessageEncoderTest extends BrambleMockTestCase {
@@ -35,8 +35,7 @@ public class MessageEncoderTest extends BrambleMockTestCase {
private final byte[] body = getRandomBytes(42); private final byte[] body = getRandomBytes(42);
private final Author author = getAuthor(); private final Author author = getAuthor();
private final BdfList authorList = new BdfList(); private final BdfList authorList = new BdfList();
private final String text = private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH);
getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
@Test @Test
public void testEncodeRequestMessage() throws FormatException { public void testEncodeRequestMessage() throws FormatException {

View File

@@ -1,98 +0,0 @@
package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.api.client.MessageQueueManager;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.test.BriarTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getClientId;
import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
import static org.junit.Assert.assertFalse;
public class MessageSenderTest extends BriarTestCase {
private final Mockery context;
private final MessageSender messageSender;
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final MetadataEncoder metadataEncoder;
private final MessageQueueManager messageQueueManager;
private final Clock clock;
public MessageSenderTest() {
context = new Mockery();
db = context.mock(DatabaseComponent.class);
clientHelper = context.mock(ClientHelper.class);
metadataEncoder =
context.mock(MetadataEncoder.class);
messageQueueManager =
context.mock(MessageQueueManager.class);
clock = context.mock(Clock.class);
messageSender = new MessageSender(db, clientHelper, clock,
metadataEncoder, messageQueueManager);
}
@Test
public void testSendMessage() throws DbException, FormatException {
Transaction txn = new Transaction(null, false);
Group privateGroup = getGroup(getClientId());
SessionId sessionId = new SessionId(getRandomId());
byte[] mac = getRandomBytes(42);
byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH);
long time = 42L;
BdfDictionary msg = BdfDictionary.of(
new BdfEntry(TYPE, TYPE_ACK),
new BdfEntry(GROUP_ID, privateGroup.getId()),
new BdfEntry(SESSION_ID, sessionId),
new BdfEntry(MAC, mac),
new BdfEntry(SIGNATURE, sig)
);
BdfList bodyList =
BdfList.of(TYPE_ACK, sessionId.getBytes(), mac, sig);
byte[] body = getRandomBytes(8);
Metadata metadata = new Metadata();
context.checking(new Expectations() {{
oneOf(clientHelper).toByteArray(bodyList);
will(returnValue(body));
oneOf(db).getGroup(txn, privateGroup.getId());
will(returnValue(privateGroup));
oneOf(metadataEncoder).encode(msg);
will(returnValue(metadata));
oneOf(clock).currentTimeMillis();
will(returnValue(time));
oneOf(messageQueueManager)
.sendMessage(txn, privateGroup, time, body, metadata);
}});
messageSender.sendMessage(txn, msg);
context.assertIsSatisfied();
assertFalse(txn.isCommitted());
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2; package org.briarproject.briar.introduction;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.briar.api.client.SessionId; import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.introduction2.IntroducerSession.Introducee; import org.briarproject.briar.introduction.IntroducerSession.Introducee;
import org.briarproject.briar.test.BriarIntegrationTestComponent; import org.briarproject.briar.test.BriarIntegrationTestComponent;
import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent; import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
import org.junit.Test; import org.junit.Test;
@@ -29,11 +29,11 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap; import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.introduction2.IntroduceeState.LOCAL_ACCEPTED; import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTHS; import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTHS;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE; import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE; import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER; import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;

View File

@@ -1,428 +0,0 @@
package org.briarproject.briar.introduction2;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.ValidatorTestCase;
import org.briarproject.briar.api.client.SessionId;
import org.jmock.Expectations;
import org.junit.Test;
import javax.annotation.Nullable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
import static org.briarproject.briar.introduction2.MessageType.ABORT;
import static org.briarproject.briar.introduction2.MessageType.ACCEPT;
import static org.briarproject.briar.introduction2.MessageType.ACTIVATE;
import static org.briarproject.briar.introduction2.MessageType.AUTH;
import static org.briarproject.briar.introduction2.MessageType.DECLINE;
import static org.briarproject.briar.introduction2.MessageType.REQUEST;
import static org.junit.Assert.assertEquals;
public class IntroductionValidatorTest extends ValidatorTestCase {
private final MessageEncoder messageEncoder =
context.mock(MessageEncoder.class);
private final IntroductionValidator validator =
new IntroductionValidator(messageEncoder, clientHelper,
metadataEncoder, clock);
private final SessionId sessionId = new SessionId(getRandomId());
private final MessageId previousMsgId = new MessageId(getRandomId());
private final String text =
getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
private final BdfDictionary meta = new BdfDictionary();
private final long acceptTimestamp = 42;
private final BdfDictionary transportProperties = BdfDictionary.of(
new BdfEntry("transportId", new BdfDictionary())
);
private final byte[] mac = getRandomBytes(MAC_BYTES);
private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES);
//
// Introduction REQUEST
//
@Test
public void testAcceptsRequest() throws Exception {
BdfList body = BdfList.of(REQUEST.getValue(), previousMsgId.getBytes(),
authorList, text);
expectParseAuthor(authorList, author);
expectEncodeRequestMetadata();
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, previousMsgId);
}
@Test
public void testAcceptsRequestWithPreviousMsgIdNull() throws Exception {
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, text);
expectParseAuthor(authorList, author);
expectEncodeRequestMetadata();
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, null);
}
@Test
public void testAcceptsRequestWithMessageNull() throws Exception {
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, null);
expectParseAuthor(authorList, author);
expectEncodeRequestMetadata();
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, null);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForRequest() throws Exception {
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForRequest() throws Exception {
BdfList body =
BdfList.of(REQUEST.getValue(), null, authorList, text, null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsRawMessageForRequest() throws Exception {
BdfList body =
BdfList.of(REQUEST.getValue(), null, authorList, getRandomId());
expectParseAuthor(authorList, author);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsStringMessageIdForRequest() throws Exception {
BdfList body =
BdfList.of(REQUEST.getValue(), "NoMessageId", authorList, null);
validator.validateMessage(message, group, body);
}
//
// Introduction ACCEPT
//
@Test
public void testAcceptsAccept() throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
acceptTimestamp, transportProperties);
context.checking(new Expectations() {{
oneOf(clientHelper).parseAndValidateTransportProperties(
transportProperties.getDictionary("transportId"));
}});
expectEncodeMetadata(ACCEPT);
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForAccept() throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForAccept() throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
acceptTimestamp, transportProperties, null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidSessionIdForAccept() throws Exception {
BdfList body =
BdfList.of(ACCEPT.getValue(), null, previousMsgId.getBytes(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp,
transportProperties);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidPreviousMsgIdForAccept() throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
null, getRandomBytes(MAX_PUBLIC_KEY_LENGTH), acceptTimestamp,
transportProperties);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongPublicKeyForAccept() throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp,
transportProperties);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsEmptyTransportPropertiesForAccept()
throws Exception {
BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), acceptTimestamp,
new BdfDictionary());
validator.validateMessage(message, group, body);
}
//
// Introduction DECLINE
//
@Test
public void testAcceptsDecline() throws Exception {
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(),
previousMsgId.getBytes());
expectEncodeMetadata(DECLINE);
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForDecline() throws Exception {
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForDecline() throws Exception {
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidSessionIdForDecline() throws Exception {
BdfList body =
BdfList.of(DECLINE.getValue(), null, previousMsgId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidPreviousMsgIdForDecline() throws Exception {
BdfList body = BdfList.of(DECLINE.getValue(), sessionId.getBytes(),
null);
validator.validateMessage(message, group, body);
}
//
// Introduction AUTH
//
@Test
public void testAcceptsAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac, signature);
expectEncodeMetadata(AUTH);
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac, signature, null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortMacForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), getRandomBytes(MAC_BYTES - 1),
signature);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongMacForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(),
getRandomBytes(MAC_BYTES + 1), signature);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidMacForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), null, signature);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortSignatureForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac, getRandomBytes(0));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongSignatureForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac,
getRandomBytes(MAX_SIGNATURE_BYTES + 1));
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidSignatureForAuth() throws Exception {
BdfList body = BdfList.of(AUTH.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), mac, null);
validator.validateMessage(message, group, body);
}
//
// Introduction ACTIVATE
//
@Test
public void testAcceptsActivate() throws Exception {
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
previousMsgId.getBytes());
expectEncodeMetadata(ACTIVATE);
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForActivate() throws Exception {
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForActivate() throws Exception {
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidSessionIdForActivate() throws Exception {
BdfList body =
BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidPreviousMsgIdForActivate() throws Exception {
BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
null);
validator.validateMessage(message, group, body);
}
//
// Introduction ABORT
//
@Test
public void testAcceptsAbort() throws Exception {
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes());
expectEncodeMetadata(ABORT);
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertExpectedContext(messageContext, previousMsgId);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortBodyForAbort() throws Exception {
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBodyForAbort() throws Exception {
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(),
previousMsgId.getBytes(), null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidSessionIdForAbort() throws Exception {
BdfList body =
BdfList.of(ABORT.getValue(), null, previousMsgId.getBytes());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsInvalidPreviousMsgIdForAbort() throws Exception {
BdfList body = BdfList.of(ABORT.getValue(), sessionId.getBytes(),
null);
validator.validateMessage(message, group, body);
}
//
// Introduction Helper Methods
//
private void expectEncodeRequestMetadata() {
context.checking(new Expectations() {{
oneOf(messageEncoder)
.encodeRequestMetadata(message.getTimestamp(), false, false,
false, false);
will(returnValue(meta));
}});
}
private void expectEncodeMetadata(MessageType type) {
context.checking(new Expectations() {{
oneOf(messageEncoder)
.encodeMetadata(type, sessionId, message.getTimestamp(),
false, false, false);
will(returnValue(meta));
}});
}
private void assertExpectedContext(BdfMessageContext c,
@Nullable MessageId dependency) {
assertEquals(meta, c.getDictionary());
if (dependency == null) {
assertEquals(0, c.getDependencies().size());
} else {
assertEquals(dependency, c.getDependencies().iterator().next());
}
}
}

View File

@@ -37,9 +37,9 @@ import org.briarproject.briar.blog.BlogModule;
import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.client.BriarClientModule;
import org.briarproject.briar.forum.ForumModule; import org.briarproject.briar.forum.ForumModule;
import org.briarproject.briar.introduction.IntroductionModule; import org.briarproject.briar.introduction.IntroductionModule;
import org.briarproject.briar.introduction2.IntroductionCryptoImplTest; import org.briarproject.briar.introduction.IntroductionCryptoImplTest;
import org.briarproject.briar.introduction2.MessageEncoderParserIntegrationTest; import org.briarproject.briar.introduction.MessageEncoderParserIntegrationTest;
import org.briarproject.briar.introduction2.SessionEncoderParserIntegrationTest; import org.briarproject.briar.introduction.SessionEncoderParserIntegrationTest;
import org.briarproject.briar.messaging.MessagingModule; import org.briarproject.briar.messaging.MessagingModule;
import org.briarproject.briar.privategroup.PrivateGroupModule; import org.briarproject.briar.privategroup.PrivateGroupModule;
import org.briarproject.briar.privategroup.invitation.GroupInvitationModule; import org.briarproject.briar.privategroup.invitation.GroupInvitationModule;