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.Toast;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
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.widget.Toast.LENGTH_SHORT;
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
implements TextInputListener {
@@ -175,7 +174,7 @@ public class IntroductionMessageFragment extends BaseFragment
ui.message.setSendButtonEnabled(false);
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);
// don't wait for the introduction to be made before finishing activity
@@ -190,7 +189,7 @@ public class IntroductionMessageFragment extends BaseFragment
try {
long timestamp = System.currentTimeMillis();
introductionManager.makeIntroduction(c1, c2, msg, timestamp);
} catch (DbException | FormatException e) {
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
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 {
/* 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
* 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 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";
String LABEL_SESSION_ID = "org.briarproject.briar.introduction/SESSION_ID";
/* Introduction Request Action */
String PUBLIC_KEY1 = "publicKey1";
String PUBLIC_KEY2 = "publicKey2";
String LABEL_MASTER_KEY = "org.briarproject.briar.introduction/MASTER_KEY";
/* Introducee Local State Metadata (without those already defined) */
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 =
String LABEL_ALICE_MAC_KEY =
"org.briarproject.briar.introduction/ALICE_MAC_KEY";
/**
* Label for deriving Bob's MAC key from the shared secret.
*/
String BOB_MAC_KEY_LABEL =
String LABEL_BOB_MAC_KEY =
"org.briarproject.briar.introduction/BOB_MAC_KEY";
/**
* Label for signing the introduction response.
*/
String SIGNING_LABEL =
"org.briarproject.briar.introduction/RESPONSE_SIGNATURE";
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";
/**
* 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;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
@@ -24,25 +23,25 @@ public interface IntroductionManager extends ConversationClient {
/**
* The current version of the introduction client.
*/
int CLIENT_VERSION = 0;
int CLIENT_VERSION = 1;
/**
* Sends two initial introduction messages.
*/
void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
long timestamp) throws DbException, FormatException;
long timestamp) throws DbException;
/**
* Accepts an introduction.
*/
void acceptIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException, FormatException;
long timestamp) throws DbException;
/**
* Declines an introduction.
*/
void declineIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException, FormatException;
long timestamp) throws DbException;
/**
* 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 static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
@Immutable
@NotNullByDefault
@@ -16,10 +16,10 @@ public class IntroductionMessage extends BaseMessageHeader {
private final SessionId sessionId;
private final MessageId messageId;
private final int role;
private final Role role;
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) {
super(messageId, groupId, time, local, sent, seen, read);
@@ -37,7 +37,7 @@ public class IntroductionMessage extends BaseMessageHeader {
}
public boolean isIntroducer() {
return role == ROLE_INTRODUCER;
return role == INTRODUCER;
}
}

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
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.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.client.SessionId;
@@ -11,19 +10,14 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
public class IntroductionAbortedEvent extends Event {
private final ContactId contactId;
private final SessionId sessionId;
public IntroductionAbortedEvent(ContactId contactId, SessionId sessionId) {
this.contactId = contactId;
public IntroductionAbortedEvent(SessionId sessionId) {
this.sessionId = sessionId;
}
public ContactId getContactId() {
return contactId;
}
public SessionId getSessionId() {
return sessionId;
}
}

View File

@@ -8,6 +8,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
// TODO still needed?
public class IntroductionSucceededEvent extends Event {
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.sync.GroupId;
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class AbortMessage extends IntroductionMessage {
class AbortMessage extends AbstractIntroductionMessage {
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.sync.GroupId;
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
abstract class IntroductionMessage {
abstract class AbstractIntroductionMessage {
private final MessageId messageId;
private final GroupId groupId;
@@ -17,7 +17,7 @@ abstract class IntroductionMessage {
@Nullable
private final MessageId previousMessageId;
IntroductionMessage(MessageId messageId, GroupId groupId,
AbstractIntroductionMessage(MessageId messageId, GroupId groupId,
long timestamp, @Nullable MessageId previousMessageId) {
this.messageId = messageId;
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.client.ClientHelper;
@@ -25,14 +25,14 @@ import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_ID;
import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION;
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.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.introduction.MessageType.DECLINE;
import static org.briarproject.briar.introduction.MessageType.REQUEST;
@Immutable
@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.plugin.TransportId;
@@ -14,7 +14,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class AcceptMessage extends IntroductionMessage {
class AcceptMessage extends AbstractIntroductionMessage {
private final SessionId sessionId;
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.sync.GroupId;
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class ActivateMessage extends IntroductionMessage {
class ActivateMessage extends AbstractIntroductionMessage {
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.sync.GroupId;
@@ -9,7 +9,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class AuthMessage extends IntroductionMessage {
class AuthMessage extends AbstractIntroductionMessage {
private final SessionId sessionId;
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.sync.GroupId;
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class DeclineMessage extends IntroductionMessage {
class DeclineMessage extends AbstractIntroductionMessage {
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.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.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.IntroductionRequest;
import org.briarproject.briar.api.introduction2.IntroductionResponse;
import org.briarproject.briar.api.introduction2.event.IntroductionRequestReceivedEvent;
import org.briarproject.briar.api.introduction2.event.IntroductionResponseReceivedEvent;
import org.briarproject.briar.api.introduction2.event.IntroductionSucceededEvent;
import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent;
import java.security.GeneralSecurityException;
import java.util.Map;
@@ -41,11 +42,11 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE;
import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_AUTH;
import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_RESPONSES;
import static org.briarproject.briar.introduction2.IntroduceeState.LOCAL_ACCEPTED;
import static org.briarproject.briar.introduction2.IntroduceeState.REMOTE_ACCEPTED;
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH;
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES;
import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED;
import static org.briarproject.briar.introduction.IntroduceeState.REMOTE_ACCEPTED;
@Immutable
@NotNullByDefault
@@ -145,10 +146,11 @@ class IntroduceeProtocolEngine
IntroduceeSession session, AcceptMessage m)
throws DbException, FormatException {
switch (session.getState()) {
case START:
return onRemoteResponseInStart(txn, session, m);
case AWAIT_RESPONSES:
case LOCAL_ACCEPTED:
return onRemoteAccept(txn, session, m);
case START:
case LOCAL_DECLINED:
case REMOTE_ACCEPTED:
case AWAIT_AUTH:
@@ -165,7 +167,7 @@ class IntroduceeProtocolEngine
throws DbException, FormatException {
switch (session.getState()) {
case START:
return session; // Ignore in the START state
return onRemoteResponseInStart(txn, session, m);
case AWAIT_RESPONSES:
case LOCAL_DECLINED:
case LOCAL_ACCEPTED:
@@ -322,18 +324,6 @@ class IntroduceeProtocolEngine
if (isInvalidDependency(s, m.getPreviousMessageId()))
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
IntroduceeState state =
s.getState() == AWAIT_RESPONSES ? REMOTE_ACCEPTED : AWAIT_AUTH;
@@ -356,12 +346,46 @@ class IntroduceeProtocolEngine
if (isInvalidDependency(s, m.getPreviousMessageId()))
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
return IntroduceeSession
.clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(),
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)
throws DbException {
boolean alice = isAlice(txn, s);
@@ -459,6 +483,9 @@ class IntroduceeProtocolEngine
if (requestId == null) throw new IllegalStateException();
markRequestUnavailableToAnswer(txn, requestId);
// Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Reset the session back to initial state
return IntroduceeSession
.clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(),
@@ -475,6 +502,9 @@ class IntroduceeProtocolEngine
// Send an ABORT message
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
return IntroduceeSession.clear(s, sent.getId(), sent.getTimestamp(),
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.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.transport.KeySetId;
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 javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.introduction2.IntroduceeState.AWAIT_ACTIVATE;
import static org.briarproject.briar.introduction2.IntroduceeState.START;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE;
import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_ACTIVATE;
import static org.briarproject.briar.introduction.IntroduceeState.START;
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
@Immutable
@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.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.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.DbException;
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.nullsafety.NotNullByDefault;
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.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.introduction2.IntroductionResponse;
import org.briarproject.briar.api.introduction2.event.IntroductionResponseReceivedEvent;
import org.briarproject.briar.introduction2.IntroducerSession.Introducee;
import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
import java.util.Map;
@@ -27,17 +29,17 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATES;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATE_A;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_ACTIVATE_B;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTHS;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTH_A;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTH_B;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSES;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSE_A;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_RESPONSE_B;
import static org.briarproject.briar.introduction2.IntroducerState.START;
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATES;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_A;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_ACTIVATE_B;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTHS;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTH_A;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTH_B;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSES;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_A;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_RESPONSE_B;
import static org.briarproject.briar.introduction.IntroducerState.START;
@Immutable
@NotNullByDefault
@@ -94,6 +96,11 @@ class IntroducerProtocolEngine
throw new UnsupportedOperationException(); // Invalid in this role
}
IntroducerSession onAbortAction(Transaction txn, IntroducerSession s)
throws DbException, FormatException {
return abort(txn, s);
}
@Override
public IntroducerSession onRequestMessage(Transaction txn,
IntroducerSession s, RequestMessage m)
@@ -111,8 +118,7 @@ class IntroducerProtocolEngine
case AWAIT_RESPONSE_B:
return onRemoteAccept(txn, s, m);
case START:
// TODO check and update lastRemoteMsgId?
return s; // Ignored in this state
return onRemoteResponseInStart(txn, s, m);
case AWAIT_AUTHS:
case AWAIT_AUTH_A:
case AWAIT_AUTH_B:
@@ -135,8 +141,7 @@ class IntroducerProtocolEngine
case AWAIT_RESPONSE_B:
return onRemoteDecline(txn, s, m);
case START:
// TODO check and update lastRemoteMsgId?
return s; // Ignored in this state
return onRemoteResponseInStart(txn, s, m);
case AWAIT_AUTHS:
case AWAIT_AUTH_A:
case AWAIT_AUTH_B:
@@ -253,14 +258,16 @@ class IntroducerProtocolEngine
if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_A;
introducee1 = new Introducee(s.getIntroducee1(), sent);
introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId());
c = contactManager.getContact(s.getIntroducee2().author.getId(),
identityManager.getLocalAuthor(txn).getId());
c = contactManager
.getContact(txn, s.getIntroducee2().author.getId(),
identityManager.getLocalAuthor(txn).getId());
} else if (i.equals(s.getIntroducee2())) {
if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_B;
introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId());
introducee2 = new Introducee(s.getIntroducee2(), sent);
c = contactManager.getContact(s.getIntroducee1().author.getId(),
identityManager.getLocalAuthor(txn).getId());
c = contactManager
.getContact(txn, s.getIntroducee1().author.getId(),
identityManager.getLocalAuthor(txn).getId());
} else throw new AssertionError();
// Broadcast IntroductionResponseReceivedEvent
@@ -296,22 +303,23 @@ class IntroducerProtocolEngine
Introducee i = getOtherIntroducee(s, m.getGroupId());
long timestamp = getLocalTimestamp(s, i);
Message sent = sendDeclineMessage(txn, i, timestamp, false);
// Track the message
messageTracker.trackOutgoingMessage(txn, sent);
// Move to the START state
Introducee introducee1, introducee2;
AuthorId localAuthorId =identityManager.getLocalAuthor(txn).getId();
Contact c;
if (i.equals(s.getIntroducee1())) {
introducee1 = new Introducee(s.getIntroducee1(), sent);
introducee2 = new Introducee(s.getIntroducee2(), m.getMessageId());
c = contactManager.getContact(s.getIntroducee2().author.getId(),
identityManager.getLocalAuthor(txn).getId());
c = contactManager
.getContact(txn, s.getIntroducee2().author.getId(),
localAuthorId);
} else if (i.equals(s.getIntroducee2())) {
introducee1 = new Introducee(s.getIntroducee1(), m.getMessageId());
introducee2 = new Introducee(s.getIntroducee2(), sent);
c = contactManager.getContact(s.getIntroducee2().author.getId(),
identityManager.getLocalAuthor(txn).getId());
c = contactManager
.getContact(txn, s.getIntroducee1().author.getId(),
localAuthorId);
} else throw new AssertionError();
// Broadcast IntroductionResponseReceivedEvent
@@ -327,6 +335,54 @@ class IntroducerProtocolEngine
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,
IntroducerSession s, AuthMessage m)
throws DbException, FormatException {
@@ -395,6 +451,9 @@ class IntroducerProtocolEngine
long timestamp = getLocalTimestamp(s, i);
Message sent = sendAbortMessage(txn, i, timestamp);
// Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Reset the session back to initial state
Introducee introducee1, introducee2;
if (i.equals(s.getIntroducee1())) {
@@ -412,6 +471,10 @@ class IntroducerProtocolEngine
IntroducerSession s) throws DbException, FormatException {
// Mark any REQUEST messages in the session unavailable to answer
markRequestsUnavailableToAnswer(txn, s);
// Broadcast abort event for testing
txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
// Send an ABORT message to both introducees
long timestamp1 = getLocalTimestamp(s, s.getIntroducee1());
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.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.MessageId;
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.concurrent.Immutable;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
@Immutable
@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.nullsafety.NotNullByDefault;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.introduction2;
package org.briarproject.briar.introduction;
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.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.FormatException;
@@ -24,14 +24,14 @@ import java.util.Map;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_ALICE_MAC_KEY;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_MAC;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_NONCE;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_SIGN;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_BOB_MAC_KEY;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_MASTER_KEY;
import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID;
import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION;
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_ALICE_MAC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_MAC;
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_NONCE;
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_SIGN;
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_BOB_MAC_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_MASTER_KEY;
import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
@Immutable
@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.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.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.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.db.Metadata;
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.sync.Client;
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.briar.api.client.MessageTracker;
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.IntroductionMessage;
import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.introduction.Role;
import org.briarproject.briar.client.ConversationClientImpl;
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
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.WARNING;
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.IntroductionConstants.ACCEPT;
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED;
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;
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.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;
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
import static org.briarproject.briar.introduction.IntroductionConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.introduction.MessageType.DECLINE;
import static org.briarproject.briar.introduction.MessageType.REQUEST;
@Immutable
@NotNullByDefault
class IntroductionManagerImpl extends ConversationClientImpl
implements IntroductionManager, Client, ContactHook {
private static final Logger LOG =
Logger.getLogger(IntroductionManagerImpl.class.getName());
private final IntroducerManager introducerManager;
private final IntroduceeManager introduceeManager;
private final IntroductionGroupFactory introductionGroupFactory;
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,
IntroducerManager introducerManager,
IntroduceeManager introduceeManager,
IntroductionGroupFactory introductionGroupFactory) {
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.introducerManager = introducerManager;
this.introduceeManager = introduceeManager;
this.introductionGroupFactory = introductionGroupFactory;
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 {
Group localGroup = introductionGroupFactory.createLocalGroup();
// Create a local group to store protocol sessions
Group localGroup = getLocalGroup();
if (db.containsGroup(txn, localGroup.getId())) return;
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);
}
@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 {
// Create an introduction group for sending introduction messages
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);
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new RuntimeException(e);
throw new AssertionError(e);
}
}
@Override
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 {
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, gId, query);
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
// delete states if introducee removes introducer
deleteMessage(txn, entry.getKey());
}
removeSessionWithIntroducer(txn, c);
abortOrRemoveSessionWithIntroducee(txn, c);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
throw new AssertionError();
}
// 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
// Remove the contact group (all messages will be removed with it)
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
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
BdfDictionary message) throws DbException, FormatException {
// 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;
public Group getContactGroup(Contact c) {
return contactGroupFactory
.createContactGroup(CLIENT_ID, CLIENT_VERSION, c);
}
@Override
public Group getContactGroup(Contact contact) {
return introductionGroupFactory.createIntroductionGroup(contact);
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();
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
public void makeIntroduction(Contact c1, Contact c2, @Nullable String msg,
long timestamp) throws DbException, FormatException {
long timestamp) throws DbException {
Transaction txn = db.startTransaction(false);
try {
introducerManager.makeIntroduction(txn, c1, c2, msg, timestamp);
Group g1 = getContactGroup(c1);
Group g2 = getContactGroup(c2);
messageTracker.trackMessage(txn, g1.getId(), timestamp, true);
messageTracker.trackMessage(txn, g2.getId(), timestamp, true);
// 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);
}
@@ -289,147 +304,78 @@ class IntroductionManagerImpl extends ConversationClientImpl
@Override
public void acceptIntroduction(ContactId contactId, SessionId sessionId,
long timestamp) throws DbException, FormatException {
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);
}
long timestamp) throws DbException {
respondToRequest(contactId, sessionId, timestamp, true);
}
@Override
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);
try {
Contact c = db.getContact(txn, contactId);
Group g = getContactGroup(c);
BdfDictionary state =
getSessionState(txn, g.getId(), sessionId.getBytes());
introduceeManager.declineIntroduction(txn, state, timestamp);
messageTracker.trackMessage(txn, g.getId(), timestamp, true);
// 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 contactId) throws DbException {
Collection<IntroductionMessage> list = new ArrayList<>();
Map<MessageId, BdfDictionary> metadata;
Collection<MessageStatus> statuses;
public Collection<IntroductionMessage> getIntroductionMessages(ContactId c)
throws DbException {
List<IntroductionMessage> messages;
Transaction txn = db.startTransaction(true);
try {
// get messages and their status
GroupId g = getContactGroup(db.getContact(txn, contactId)).getId();
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
statuses = db.getMessageStatus(txn, contactId, g);
// turn messages into classes for the UI
for (MessageStatus s : statuses) {
MessageId messageId = s.getMessageId();
BdfDictionary msg = metadata.get(messageId);
if (msg == null) continue;
try {
long type = msg.getLong(TYPE);
if (type == TYPE_ACK || type == TYPE_ABORT) continue;
// get session state
SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
BdfDictionary state =
getSessionState(txn, g, sessionId.getBytes());
int role = state.getLong(ROLE).intValue();
boolean local;
long time = msg.getLong(MESSAGE_TIME);
boolean accepted = msg.getBoolean(ACCEPT, false);
boolean read = msg.getBoolean(MSG_KEY_READ, false);
AuthorId authorId;
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);
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);
@@ -438,88 +384,129 @@ class IntroductionManagerImpl extends ConversationClientImpl
} finally {
db.endTransaction(txn);
}
return list;
return messages;
}
private String getNameForIntroducer(ContactId contactId,
BdfDictionary state) throws FormatException {
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)
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
try {
// See if we can find the state directly for the introducer
BdfDictionary state = clientHelper
.getMessageMetadataAsDictionary(txn,
new MessageId(sessionId));
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)) {
throw new NoSuchMessageException();
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;
}
return state;
} catch (NoSuchMessageException e) {
// State not found directly, so iterate over all states
// to find state for introducee
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn,
introductionGroupFactory.createLocalGroup().getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
if (Arrays.equals(m.getValue().getRaw(SESSION_ID), sessionId)) {
BdfDictionary state = m.getValue();
GroupId g = new GroupId(state.getRaw(GROUP_ID));
if (g.equals(groupId)) return state;
}
}
if (warn && LOG.isLoggable(WARNING))
LOG.warning("No session state found");
throw new FormatException();
} 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 void removeSessionWithIntroducer(Transaction txn,
Contact introducer) throws DbException, FormatException {
BdfDictionary query = sessionEncoder
.getIntroduceeSessionsByIntroducerQuery(introducer.getAuthor());
Map<MessageId, BdfDictionary> sessions = clientHelper
.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,
byte[] sessionId) throws DbException, FormatException {
return getSessionState(txn, groupId, sessionId, true);
private void abortOrRemoveSessionWithIntroducee(Transaction txn,
Contact c) throws DbException, FormatException {
BdfDictionary query = sessionEncoder.getIntroducerSessionsQuery();
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)
throws DbException {
private void abortOrRemoveSessionWithIntroducee(Transaction txn,
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);
db.deleteMessageMetadata(txn, messageId);
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

@@ -4,8 +4,8 @@ import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.ValidationManager;
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.messaging.ConversationManager;
@@ -21,22 +21,22 @@ import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT
public class IntroductionModule {
public static class EagerSingletons {
@Inject
IntroductionManager introductionManager;
@Inject
IntroductionValidator introductionValidator;
@Inject
IntroductionManager introductionManager;
}
@Provides
@Singleton
IntroductionValidator provideValidator(
MessageQueueManager messageQueueManager,
MetadataEncoder metadataEncoder, ClientHelper clientHelper,
Clock clock) {
IntroductionValidator provideValidator(ValidationManager validationManager,
MessageEncoder messageEncoder, MetadataEncoder metadataEncoder,
ClientHelper clientHelper, Clock clock) {
IntroductionValidator introductionValidator = new IntroductionValidator(
clientHelper, metadataEncoder, clock);
messageQueueManager.registerMessageValidator(CLIENT_ID,
IntroductionValidator introductionValidator =
new IntroductionValidator(messageEncoder, clientHelper,
metadataEncoder, clock);
validationManager.registerMessageValidator(CLIENT_ID,
introductionValidator);
return introductionValidator;
@@ -46,16 +46,42 @@ public class IntroductionModule {
@Singleton
IntroductionManager provideIntroductionManager(
LifecycleManager lifecycleManager, ContactManager contactManager,
MessageQueueManager messageQueueManager,
ValidationManager validationManager,
ConversationManager conversationManager,
IntroductionManagerImpl introductionManager) {
lifecycleManager.registerClient(introductionManager);
contactManager.registerContactHook(introductionManager);
messageQueueManager.registerIncomingMessageHook(CLIENT_ID,
validationManager.registerIncomingMessageHook(CLIENT_ID,
introductionManager);
conversationManager.registerConversationClient(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;
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;
@@ -9,183 +11,166 @@ 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 org.briarproject.briar.client.BdfQueueMessageValidator;
import java.util.Collections;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
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_SIGNATURE_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.checkSize;
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.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;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.AUTH;
@Immutable
@NotNullByDefault
class IntroductionValidator extends BdfQueueMessageValidator {
class IntroductionValidator extends BdfMessageValidator {
IntroductionValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
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());
BdfDictionary d;
long type = body.getLong(0);
byte[] id = body.getRaw(1);
checkLength(id, SessionId.LENGTH);
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();
}
}
if (type == TYPE_REQUEST) {
d = validateRequest(body);
} else if (type == TYPE_RESPONSE) {
d = validateResponse(body);
} else if (type == TYPE_ACK) {
d = validateAck(body);
} else if (type == TYPE_ABORT) {
d = validateAbort(body);
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 {
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 {
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
String name = message.getString(2);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
byte[] ephemeralPublicKey = body.getRaw(3);
checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH);
// parse contact's public key
byte[] key = message.getRaw(3);
checkLength(key, 0, MAX_PUBLIC_KEY_LENGTH);
body.getLong(4);
// parse (optional) message
String msg = null;
if (message.size() == 5) {
msg = message.getString(4);
checkLength(msg, 0, MAX_INTRODUCTION_MESSAGE_LENGTH);
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);
}
// Return the metadata
BdfDictionary d = new BdfDictionary();
d.put(NAME, name);
d.put(PUBLIC_KEY, key);
if (msg != null) {
d.put(MSG, msg);
}
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);
}
}
SessionId sessionId = new SessionId(sessionIdBytes);
BdfDictionary meta = messageEncoder
.encodeMetadata(ACCEPT, sessionId, m.getTimestamp(), false,
false, false);
if (previousMessageId == null) {
return new BdfMessageContext(meta);
} 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 {
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)
private BdfMessageContext validateAuthMessage(Message m, BdfList body)
throws FormatException {
checkSize(body, 5);
checkSize(message, 2);
byte[] sessionIdBytes = body.getRaw(1);
checkLength(sessionIdBytes, UniqueId.LENGTH);
// Return the metadata
return new BdfDictionary();
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.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.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.client.ClientHelper;
@@ -20,19 +20,19 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
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.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
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.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.introduction.MessageType.DECLINE;
import static org.briarproject.briar.introduction.MessageType.REQUEST;
@NotNullByDefault
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.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.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.client.ClientHelper;
@@ -18,14 +18,14 @@ import java.util.Map;
import javax.inject.Inject;
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.introduction2.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_SESSION_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
import static org.briarproject.briar.introduction2.MessageType.REQUEST;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_INVITATION_ACCEPTED;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_SESSION_ID;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.introduction.IntroductionConstants.MSG_KEY_VISIBLE_IN_UI;
import static org.briarproject.briar.introduction.MessageType.REQUEST;
@NotNullByDefault
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.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.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.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.nullsafety.NotNullByDefault;
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class RequestMessage extends IntroductionMessage {
class RequestMessage extends AbstractIntroductionMessage {
private final Author author;
@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.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.Role;
import org.briarproject.briar.api.introduction.Role;
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.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface SessionEncoder {
BdfDictionary getIntroduceeSessionsByIntroducerQuery(Author introducer);
BdfDictionary getIntroducerSessionsQuery();
BdfDictionary encodeIntroducerSession(IntroducerSession 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.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.plugin.TransportId;
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;
@@ -14,28 +16,30 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
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.introduction2.IntroductionConstants.SESSION_KEY_AUTHOR;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_GROUP_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_1;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_2;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCER;
import static org.briarproject.briar.introduction2.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.introduction2.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_MASTER_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_SESSION_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_STATE;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES;
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_GROUP_ID;
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_INTRODUCER;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID;
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
@NotNullByDefault
@@ -48,6 +52,23 @@ class SessionEncoderImpl implements SessionEncoder {
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
public BdfDictionary encodeIntroducerSession(IntroducerSession 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.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.Role;
import org.briarproject.briar.api.introduction.Role;
@NotNullByDefault
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.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.transport.KeySetId;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.introduction2.Role;
import org.briarproject.briar.introduction2.IntroducerSession.Introducee;
import org.briarproject.briar.api.introduction.Role;
import org.briarproject.briar.introduction.IntroducerSession.Introducee;
import java.util.HashMap;
import java.util.Map;
@@ -22,30 +22,30 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_AUTHOR;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_GROUP_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_1;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCEE_2;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_INTRODUCER;
import static org.briarproject.briar.introduction2.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.introduction2.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_MASTER_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_SESSION_ID;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_STATE;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_GROUP_ID;
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_INTRODUCER;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID;
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;
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
@Immutable
@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.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.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.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;
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.contact.Contact;
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.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
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.event.Event;
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.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
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.test.TestDatabaseModule;
import org.briarproject.briar.api.client.SessionId;
@@ -38,56 +36,35 @@ import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeoutException;
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.test.TestPluginConfigModule.TRANSPORT_ID;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY;
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.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.bramble.test.TestUtils.getTransportProperties;
import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
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.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class IntroductionIntegrationTest
extends BriarIntegrationTest<IntroductionIntegrationTestComponent> {
@Inject
IntroductionGroupFactory introductionGroupFactory;
// objects accessed from background threads need to be volatile
private volatile IntroductionManager introductionManager0;
private volatile IntroductionManager introductionManager1;
@@ -102,7 +79,7 @@ public class IntroductionIntegrationTest
Logger.getLogger(IntroductionIntegrationTest.class.getName());
interface StateVisitor {
boolean visit(BdfDictionary response);
AcceptMessage visit(AcceptMessage response);
}
@Before
@@ -151,50 +128,50 @@ public class IntroductionIntegrationTest
.makeIntroduction(introducee1, introducee2, "Hi!", time);
// check that messages are tracked properly
Group g1 = introductionGroupFactory
.createIntroductionGroup(introducee1);
Group g2 = introductionGroupFactory
.createIntroductionGroup(introducee2);
assertGroupCount(messageTracker0, g1.getId(), 1, 0, time);
assertGroupCount(messageTracker0, g2.getId(), 1, 0, time);
Group g1 = introductionManager0.getContactGroup(introducee1);
Group g2 = introductionManager0.getContactGroup(introducee2);
assertGroupCount(messageTracker0, g1.getId(), 1, 0);
assertGroupCount(messageTracker0, g2.getId(), 1, 0);
// sync first request message
// sync first REQUEST message
sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
assertGroupCount(messageTracker1, g1.getId(), 2, 1);
// sync second request message
// sync second REQUEST message
sync0To2(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener2.requestReceived);
assertGroupCount(messageTracker2, g2.getId(), 2, 1);
// sync first response
// sync first ACCEPT message
sync1To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response1Received);
assertGroupCount(messageTracker0, g1.getId(), 2, 1);
// sync second response
// sync second ACCEPT message
sync2To0(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response2Received);
assertGroupCount(messageTracker0, g2.getId(), 2, 1);
// sync forwarded responses to introducees
// sync forwarded ACCEPT messages to introducees
sync0To1(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);
sync0To2(1, true);
// sync second ACK and its forward
sync2To0(1, true);
sync0To1(1, true);
// sync second AUTH and its forward as well as the following ACTIVATE
sync2To0(2, true);
sync0To1(2, true);
// sync first ACTIVATE and its forward
sync1To0(1, true);
sync0To2(1, true);
// wait for introduction to succeed
eventWaiter.await(TIMEOUT, 2);
@@ -269,10 +246,8 @@ public class IntroductionIntegrationTest
assertFalse(contactManager2
.contactExists(author1.getId(), author2.getId()));
Group g1 = introductionGroupFactory
.createIntroductionGroup(introducee1);
Group g2 = introductionGroupFactory
.createIntroductionGroup(introducee2);
Group g1 = introductionManager0.getContactGroup(introducee1);
Group g2 = introductionManager0.getContactGroup(introducee2);
assertEquals(2,
introductionManager0.getIntroductionMessages(contactId1From0)
.size());
@@ -290,6 +265,10 @@ public class IntroductionIntegrationTest
introductionManager2.getIntroductionMessages(contactId0From2)
.size());
assertGroupCount(messageTracker2, g2.getId(), 3, 2);
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
}
@Test
@@ -342,6 +321,9 @@ public class IntroductionIntegrationTest
assertEquals(2,
introductionManager2.getIntroductionMessages(contactId0From2)
.size());
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
}
@Test
@@ -393,6 +375,9 @@ public class IntroductionIntegrationTest
// since introducee2 was already in FINISHED state when
// introducee1's response arrived, she ignores and deletes it
assertDefaultUiMessages();
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
}
@Test
@@ -432,6 +417,8 @@ public class IntroductionIntegrationTest
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.response2Received);
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
}
@Test
@@ -452,61 +439,11 @@ public class IntroductionIntegrationTest
// make really sure we don't have that request
assertTrue(introductionManager1.getIntroductionMessages(contactId0From1)
.isEmpty());
}
@Test
public void testSessionIdReuse() throws Exception {
addListeners(true, true);
// 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);
// The message was invalid, so no abort message was sent
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertFalse(listener2.aborted);
}
@Test
@@ -523,34 +460,20 @@ public class IntroductionIntegrationTest
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// get database and local group for introducee
Group group1 = introductionGroupFactory.createLocalGroup();
// get local group for introducee1
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
assertEquals(1, map.size());
assertEquals(1, c1.getClientHelper()
.getMessageMetadataAsDictionary(group1.getId()).size());
// introducee1 removes introducer
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
assertEquals(0, map.size());
assertEquals(0, c1.getClientHelper()
.getMessageMetadataAsDictionary(group1.getId()).size());
}
@Test
@@ -567,48 +490,36 @@ public class IntroductionIntegrationTest
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// get database and local group for introducee
Group group1 = introductionGroupFactory.createLocalGroup();
// get local group for introducer
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
assertEquals(1, map.size());
assertEquals(1, c0.getClientHelper()
.getMessageMetadataAsDictionary(group0.getId()).size());
// introducer removes introducee1
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
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
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
assertEquals(0, map.size());
assertEquals(0, c0.getClientHelper()
.getMessageMetadataAsDictionary(group0.getId()).size());
}
private void testModifiedResponse(StateVisitor visitor)
@@ -630,26 +541,36 @@ public class IntroductionIntegrationTest
eventWaiter.await(TIMEOUT, 1);
// get response to be forwarded
ClientHelper ch = c0.getClientHelper(); // need 0's ClientHelper here
Entry<MessageId, BdfDictionary> resp =
getMessageFor(ch, contact2From0, TYPE_RESPONSE);
MessageId responseId = resp.getKey();
BdfDictionary response = resp.getValue();
// adapt outgoing message queue to removed message
Group g2 = introductionGroupFactory
.createIntroductionGroup(contact2From0);
decreaseOutgoingMessageCounter(ch, g2.getId());
AcceptMessage message =
(AcceptMessage) getMessageFor(c0.getClientHelper(),
contact2From0, ACCEPT);
// allow visitor to modify response
boolean earlyAbort = visitor.visit(response);
AcceptMessage m = visitor.visit(message);
// replace original response with modified one
MessageSender sender0 = c0.getMessageSender();
Transaction txn = db0.startTransaction(false);
try {
db0.deleteMessage(txn, responseId);
sender0.sendMessage(txn, response);
db0.removeMessage(txn, message.getMessageId());
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);
} finally {
db0.endTransaction(txn);
@@ -663,21 +584,14 @@ public class IntroductionIntegrationTest
sync0To1(1, true);
sync0To2(1, true);
// sync first ACK and forward it
// sync first AUTH and forward it
sync1To0(1, true);
sync0To2(1, true);
// introducee2 should have detected the fake now
// and deleted introducee1 again
Collection<Contact> contacts2;
txn = db2.startTransaction(true);
try {
contacts2 = db2.getContacts(txn);
db2.commitTransaction(txn);
} finally {
db2.endTransaction(txn);
}
assertEquals(1, contacts2.size());
assertFalse(listener0.aborted);
assertFalse(listener1.aborted);
assertTrue(listener2.aborted);
// sync introducee2's ack and following abort
sync2To0(2, true);
@@ -687,144 +601,44 @@ public class IntroductionIntegrationTest
// sync abort messages to introducees
sync0To1(2, true);
sync0To2(1, true);
if (earlyAbort) {
assertTrue(listener1.aborted);
assertTrue(listener2.aborted);
} else {
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());
}
// ensure everybody got the abort now
assertTrue(listener0.aborted);
assertTrue(listener1.aborted);
assertTrue(listener2.aborted);
}
@Test
public void testModifiedTransportProperties() throws Exception {
testModifiedResponse(response -> {
BdfDictionary tp = response.getDictionary(TRANSPORT, null);
tp.put("fakeId", BdfDictionary.of(new BdfEntry("fake", "fake")));
response.put(TRANSPORT, tp);
return false;
});
testModifiedResponse(
m -> new AcceptMessage(m.getMessageId(), m.getGroupId(),
m.getTimestamp(), m.getPreviousMessageId(),
m.getSessionId(), m.getEphemeralPublicKey(),
m.getAcceptTimestamp(),
getTransportPropertiesMap(2))
);
}
@Test
public void testModifiedTimestamp() throws Exception {
testModifiedResponse(response -> {
long timestamp = response.getLong(TIME, 0L);
response.put(TIME, timestamp + 1);
return false;
});
testModifiedResponse(
m -> new AcceptMessage(m.getMessageId(), m.getGroupId(),
m.getTimestamp(), m.getPreviousMessageId(),
m.getSessionId(), m.getEphemeralPublicKey(),
clock.currentTimeMillis(),
m.getTransportProperties())
);
}
@Test
public void testModifiedEphemeralPublicKey() throws Exception {
testModifiedResponse(response -> {
KeyPair keyPair = crypto.generateAgreementKeyPair();
response.put(E_PUBLIC_KEY, keyPair.getPublic().getEncoded());
return true;
});
}
@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
}
testModifiedResponse(
m -> new AcceptMessage(m.getMessageId(), m.getGroupId(),
m.getTimestamp(), m.getPreviousMessageId(),
m.getSessionId(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
m.getAcceptTimestamp(), m.getTransportProperties())
);
}
private void addTransportProperties()
@@ -832,17 +646,15 @@ public class IntroductionIntegrationTest
TransportPropertyManager tpm0 = c0.getTransportPropertyManager();
TransportPropertyManager tpm1 = c1.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);
sync0To2(1, true);
tpm1.mergeLocalProperties(TRANSPORT_ID, tp);
tpm1.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
sync1To0(1, true);
tpm2.mergeLocalProperties(TRANSPORT_ID, tp);
tpm2.mergeLocalProperties(TRANSPORT_ID, getTransportProperties(2));
sync2To0(1, true);
}
@@ -935,7 +747,7 @@ public class IntroductionIntegrationTest
time);
}
}
} catch (DbException | FormatException exception) {
} catch (DbException exception) {
eventWaiter.rethrow(exception);
} finally {
eventWaiter.resume();
@@ -945,7 +757,6 @@ public class IntroductionIntegrationTest
Contact contact = ((IntroductionSucceededEvent) e).getContact();
eventWaiter
.assertFalse(contact.getId().equals(contactId0From1));
eventWaiter.assertTrue(contact.isActive());
eventWaiter.resume();
} else if (e instanceof IntroductionAbortedEvent) {
aborted = true;
@@ -981,30 +792,41 @@ public class IntroductionIntegrationTest
}
private void decreaseOutgoingMessageCounter(ClientHelper ch, GroupId g)
throws FormatException, DbException {
BdfDictionary gD = ch.getGroupMetadataAsDictionary(g);
LOG.warning(gD.toString());
BdfDictionary queue = gD.getDictionary(QUEUE_STATE_KEY);
queue.put("nextOut", queue.getLong("nextOut") - 1);
gD.put(QUEUE_STATE_KEY, queue);
ch.mergeGroupMetadata(g, gD);
private void replacePreviousLocalMessageId(Author author,
BdfDictionary d, MessageId id) throws FormatException {
BdfDictionary i1 = d.getDictionary(SESSION_KEY_INTRODUCEE_1);
BdfDictionary i2 = d.getDictionary(SESSION_KEY_INTRODUCEE_2);
Author a1 = clientHelper
.parseAndValidateAuthor(i1.getList(SESSION_KEY_AUTHOR));
Author a2 = clientHelper
.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,
Contact contact, long type) throws FormatException, DbException {
Entry<MessageId, BdfDictionary> response = null;
Group g = introductionGroupFactory
.createIntroductionGroup(contact);
private AbstractIntroductionMessage getMessageFor(ClientHelper ch,
Contact contact, MessageType type)
throws FormatException, DbException {
Group g = introductionManager0.getContactGroup(contact);
BdfDictionary query = BdfDictionary.of(
new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue())
);
Map<MessageId, BdfDictionary> map =
ch.getMessageMetadataAsDictionary(g.getId());
for (Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
if (entry.getValue().getLong(TYPE) == type) {
response = entry;
}
}
assertTrue(response != null);
return response;
ch.getMessageMetadataAsDictionary(g.getId(), query);
assertEquals(1, map.size());
MessageId id = map.entrySet().iterator().next().getKey();
Message m = ch.getMessage(id);
BdfList body = ch.getMessageAsList(id);
//noinspection ConstantConditions
return c0.getMessageParser().parseAcceptMessage(m, body);
}
}

View File

@@ -59,6 +59,7 @@ interface IntroductionIntegrationTestComponent
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;
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.BdfEntry;
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.system.Clock;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.ValidatorTestCase;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.test.BriarTestCase;
import org.jmock.Mockery;
import org.jmock.Expectations;
import org.junit.Test;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
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.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.getRandomId;
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.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_LENGTH;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
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;
import static org.junit.Assert.assertArrayEquals;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.introduction.MessageType.ACCEPT;
import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
import static org.briarproject.briar.introduction.MessageType.AUTH;
import static org.briarproject.briar.introduction.MessageType.DECLINE;
import static org.briarproject.briar.introduction.MessageType.REQUEST;
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 Group group;
private final Message message;
private final IntroductionValidator validator;
private final Clock clock = new SystemClock();
private final MessageEncoder messageEncoder =
context.mock(MessageEncoder.class);
private final IntroductionValidator validator =
new IntroductionValidator(messageEncoder, clientHelper,
metadataEncoder, clock);
public IntroductionValidatorTest() {
group = getGroup(getClientId());
MessageId messageId = new MessageId(getRandomId());
long timestamp = System.currentTimeMillis();
byte[] raw = getRandomBytes(123);
message = new Message(messageId, group.getId(), timestamp, raw);
ClientHelper clientHelper = context.mock(ClientHelper.class);
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
validator = new IntroductionValidator(clientHelper, metadataEncoder,
clock);
context.assertIsSatisfied();
}
private final SessionId sessionId = new SessionId(getRandomId());
private final MessageId previousMsgId = new MessageId(getRandomId());
private final String text = getRandomString(MAX_REQUEST_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 Requests
// Introduction REQUEST
//
@Test
public void testValidateProperIntroductionRequest() throws Exception {
byte[] sessionId = getRandomId();
String name = getRandomString(MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
String text = getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
public void testAcceptsRequest() throws Exception {
BdfList body = BdfList.of(REQUEST.getValue(), previousMsgId.getBytes(),
authorList, text);
BdfList body = BdfList.of(TYPE_REQUEST, sessionId,
name, publicKey, text);
expectParseAuthor(authorList, author);
expectEncodeRequestMetadata();
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
BdfDictionary result =
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();
assertExpectedContext(messageContext, previousMsgId);
}
@Test
public void testValidateIntroductionDeclineResponse() throws Exception {
BdfDictionary msg = getValidIntroductionResponse(false);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT));
public void testAcceptsRequestWithPreviousMsgIdNull() throws Exception {
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList, text);
BdfDictionary result = validator.validateMessage(message, group, body)
.getDictionary();
expectParseAuthor(authorList, author);
expectEncodeRequestMetadata();
BdfMessageContext messageContext =
validator.validateMessage(message, group, body);
assertFalse(result.getBoolean(ACCEPT));
context.assertIsSatisfied();
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 testValidateIntroductionResponseWithoutAccept()
throws Exception {
BdfDictionary msg = getValidIntroductionResponse(false);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
public void testRejectsTooShortBodyForRequest() throws Exception {
BdfList body = BdfList.of(REQUEST.getValue(), null, authorList);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateIntroductionResponseWithBrokenTp()
throws Exception {
BdfDictionary msg = getValidIntroductionResponse(true);
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));
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 testValidateIntroductionResponseWithoutPublicKey()
throws Exception {
BdfDictionary msg = getValidIntroductionResponse(true);
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
msg.getBoolean(ACCEPT), msg.getLong(TIME),
msg.getDictionary(TRANSPORT));
public void testRejectsRawMessageForRequest() throws Exception {
BdfList body =
BdfList.of(REQUEST.getValue(), null, authorList, getRandomId());
expectParseAuthor(authorList, author);
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 {
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();
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)
);
//
// Introduction DECLINE
//
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, TYPE_RESPONSE);
msg.put(GROUP_ID, groupId);
msg.put(SESSION_ID, sessionId);
msg.put(ACCEPT, accept);
if (accept) {
msg.put(TIME, time);
msg.put(E_PUBLIC_KEY, publicKey);
msg.put(TRANSPORT, tp);
@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(), 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.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.getTransportPropertiesMap;
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.REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
import static org.briarproject.briar.introduction.MessageType.ABORT;
import static org.briarproject.briar.introduction.MessageType.REQUEST;
import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -55,8 +55,7 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
private final SessionId sessionId = new SessionId(getRandomId());
private final MessageId previousMsgId = new MessageId(getRandomId());
private final Author author;
private final String text =
getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH);
private final byte[] ephemeralPublicKey =
getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
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.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.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.REQUEST;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
import static org.briarproject.briar.introduction.MessageType.REQUEST;
public class MessageEncoderTest extends BrambleMockTestCase {
@@ -35,8 +35,7 @@ public class MessageEncoderTest extends BrambleMockTestCase {
private final byte[] body = getRandomBytes(42);
private final Author author = getAuthor();
private final BdfList authorList = new BdfList();
private final String text =
getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH);
@Test
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.client.ClientHelper;
@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.test.BrambleTestCase;
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.DaggerBriarIntegrationTestComponent;
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.getTransportPropertiesMap;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.introduction2.IntroduceeState.LOCAL_ACCEPTED;
import static org.briarproject.briar.introduction2.IntroducerState.AWAIT_AUTHS;
import static org.briarproject.briar.introduction2.IntroductionConstants.SESSION_KEY_ROLE;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction2.Role.INTRODUCER;
import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED;
import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTHS;
import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
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.forum.ForumModule;
import org.briarproject.briar.introduction.IntroductionModule;
import org.briarproject.briar.introduction2.IntroductionCryptoImplTest;
import org.briarproject.briar.introduction2.MessageEncoderParserIntegrationTest;
import org.briarproject.briar.introduction2.SessionEncoderParserIntegrationTest;
import org.briarproject.briar.introduction.IntroductionCryptoImplTest;
import org.briarproject.briar.introduction.MessageEncoderParserIntegrationTest;
import org.briarproject.briar.introduction.SessionEncoderParserIntegrationTest;
import org.briarproject.briar.messaging.MessagingModule;
import org.briarproject.briar.privategroup.PrivateGroupModule;
import org.briarproject.briar.privategroup.invitation.GroupInvitationModule;