Massive refactoring to use pseudonyms instead of nicknames for contacts.

The invitation and private messaging UIs are currently broken. Some key
rotation bugs were fixed; others may have been created (unit tests
needed). An encoding for private keys was added. Pseudonyms were moved
out of the messaging package and ratings were moved in.
This commit is contained in:
akwizgran
2013-03-29 19:48:23 +00:00
parent 4a40de957c
commit 3309938467
131 changed files with 2094 additions and 1398 deletions

View File

@@ -13,7 +13,7 @@
<string name="contact_connected">Connected</string>
<string name="format_contact_last_connected">Last connected &lt;br /&gt; %1$s</string>
<string name="add_contact_title">Add a Contact</string>
<string name="same_network">Briar can add contacts via Wi-Fi or Bluetooth. To use Wi-Fi you must both be connected to the same network.</string>
<string name="choose_identity">Choose an identity to use with this contact:</string>
<string name="wifi_not_available">Wi-Fi is not available on this device</string>
<string name="wifi_disabled">Wi-Fi is OFF</string>
<string name="wifi_disconnected">Wi-Fi is DISCONNECTED</string>

View File

@@ -19,8 +19,8 @@ import net.sf.briar.android.widgets.CommonLayoutParams;
import net.sf.briar.android.widgets.HorizontalBorder;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.event.ContactAddedEvent;
import net.sf.briar.api.db.event.ContactRemovedEvent;
@@ -52,7 +52,7 @@ implements OnClickListener, DatabaseListener, ConnectionListener {
// Fields that are accessed from DB threads must be volatile
@Inject private volatile DatabaseComponent db;
@Inject @DatabaseExecutor private volatile Executor dbExecutor;
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
@Override
public void onCreate(Bundle state) {
@@ -87,36 +87,6 @@ implements OnClickListener, DatabaseListener, ConnectionListener {
// Bind to the service so we can wait for the DB to be opened
bindService(new Intent(BriarService.class.getName()),
serviceConnection, 0);
// Add some fake contacts to the database in a background thread
insertFakeContacts();
}
// FIXME: Remove this
private void insertFakeContacts() {
dbExecutor.execute(new Runnable() {
public void run() {
try {
// Wait for the service to be bound and started
serviceConnection.waitForStartup();
// If there are no contacts in the DB, create some fake ones
Collection<Contact> contacts = db.getContacts();
if(contacts.isEmpty()) {
if(LOG.isLoggable(INFO))
LOG.info("Inserting fake contacts");
db.addContact("Alice");
db.addContact("Bob");
}
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} catch(InterruptedException e) {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while waiting for service");
Thread.currentThread().interrupt();
}
}
});
}
@Override
@@ -126,7 +96,7 @@ implements OnClickListener, DatabaseListener, ConnectionListener {
}
private void loadContacts() {
dbExecutor.execute(new Runnable() {
dbUiExecutor.execute(new Runnable() {
public void run() {
try {
// Wait for the service to be bound and started

View File

@@ -19,7 +19,7 @@ class ContactListItem {
}
String getContactName() {
return contact.getName();
return contact.getAuthor().getName();
}
long getLastConnected() {

View File

@@ -17,6 +17,7 @@ import net.sf.briar.android.BriarService;
import net.sf.briar.android.BriarService.BriarServiceConnection;
import net.sf.briar.android.widgets.CommonLayoutParams;
import net.sf.briar.android.widgets.HorizontalBorder;
import net.sf.briar.api.Author;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
@@ -28,7 +29,6 @@ import net.sf.briar.api.db.event.GroupMessageAddedEvent;
import net.sf.briar.api.db.event.MessageExpiredEvent;
import net.sf.briar.api.db.event.RatingChangedEvent;
import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.GroupId;
import android.content.Intent;
import android.os.Bundle;

View File

@@ -6,17 +6,17 @@ import static android.view.View.INVISIBLE;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import static java.text.DateFormat.SHORT;
import static net.sf.briar.api.Rating.GOOD;
import static net.sf.briar.api.Rating.UNRATED;
import static net.sf.briar.api.messaging.Rating.GOOD;
import static net.sf.briar.api.messaging.Rating.UNRATED;
import java.util.ArrayList;
import net.sf.briar.R;
import net.sf.briar.android.widgets.CommonLayoutParams;
import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.api.Rating;
import net.sf.briar.api.Author;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.Rating;
import net.sf.briar.util.StringUtils;
import android.content.Context;
import android.content.res.Resources;

View File

@@ -6,15 +6,8 @@ import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.BAD;
import static net.sf.briar.api.Rating.GOOD;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
@@ -29,11 +22,8 @@ import net.sf.briar.android.BriarService.BriarServiceConnection;
import net.sf.briar.android.widgets.CommonLayoutParams;
import net.sf.briar.android.widgets.HorizontalBorder;
import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.db.NoSuchSubscriptionException;
@@ -42,13 +32,8 @@ import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.db.event.GroupMessageAddedEvent;
import net.sf.briar.api.db.event.MessageExpiredEvent;
import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorFactory;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupFactory;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageFactory;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
@@ -73,13 +58,8 @@ implements OnClickListener, DatabaseListener {
private ImageButton newGroupButton = null, composeButton = null;
// Fields that are accessed from DB threads must be volatile
@Inject private volatile CryptoComponent crypto;
@Inject private volatile DatabaseComponent db;
@Inject @DatabaseExecutor private volatile Executor dbExecutor;
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
@Inject private volatile AuthorFactory authorFactory;
@Inject private volatile GroupFactory groupFactory;
@Inject private volatile MessageFactory messageFactory;
private volatile boolean restricted = false;
@Override
@@ -134,154 +114,6 @@ implements OnClickListener, DatabaseListener {
// Bind to the service so we can wait for the DB to be opened
bindService(new Intent(BriarService.class.getName()),
serviceConnection, 0);
// Add some fake messages to the database in a background thread
insertFakeMessages();
}
// FIXME: Remove this
private void insertFakeMessages() {
dbExecutor.execute(new Runnable() {
public void run() {
try {
// Wait for the service to be bound and started
serviceConnection.waitForStartup();
// If there are no groups in the DB, create some fake ones
Collection<Group> groups = db.getSubscriptions();
if(!groups.isEmpty()) return;
if(LOG.isLoggable(INFO))
LOG.info("Inserting fake groups and messages");
// We'll also need a contact to receive messages from
ContactId contactId = db.addContact("Dave");
// Finally, we'll need some authors for the messages
long now = System.currentTimeMillis();
KeyPair keyPair = crypto.generateSignatureKeyPair();
long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO))
LOG.info("Key generation took " + duration + " ms");
byte[] publicKey = keyPair.getPublic().getEncoded();
PrivateKey privateKey = keyPair.getPrivate();
Author author = authorFactory.createAuthor("Batman",
publicKey);
db.setRating(author.getId(), BAD);
Author author1 = authorFactory.createAuthor("Duckman",
publicKey);
db.setRating(author1.getId(), GOOD);
// Insert some fake groups and make them visible
Group group = groupFactory.createGroup("DisneyLeaks");
db.subscribe(group);
db.setVisibility(group.getId(), Arrays.asList(contactId));
Group group1 = groupFactory.createGroup("Godwin's Lore");
db.subscribe(group1);
db.setVisibility(group1.getId(), Arrays.asList(contactId));
Group group2 = groupFactory.createGroup(
"All Kids Love Blog", publicKey);
db.subscribe(group2);
db.setVisibility(group2.getId(), Arrays.asList(contactId));
// Insert some text messages to the unrestricted groups
for(int i = 0; i < 20; i++) {
String body;
if(i % 3 == 0) {
body = "Message " + i + " is short.";
} else {
body = "Message " + i + " is long enough to wrap"
+ " onto a second line on some screens.";
}
Group g = i % 2 == 0 ? group : group1;
Message m;
now = System.currentTimeMillis();
if(i % 5 == 0) {
m = messageFactory.createAnonymousMessage(null, g,
"text/plain", body.getBytes("UTF-8"));
} else if(i % 5 == 2) {
m = messageFactory.createPseudonymousMessage(null,
g, author, privateKey, "text/plain",
body.getBytes("UTF-8"));
} else {
m = messageFactory.createPseudonymousMessage(null,
g, author1, privateKey, "text/plain",
body.getBytes("UTF-8"));
}
duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) {
LOG.info("Message creation took " +
duration + " ms");
}
now = System.currentTimeMillis();
if(Math.random() < 0.5) db.addLocalGroupMessage(m);
else db.receiveMessage(contactId, m);
db.setReadFlag(m.getId(), i % 4 == 0);
duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) {
LOG.info("Message storage took " +
duration + " ms");
}
}
// Insert a non-text message
Message m = messageFactory.createAnonymousMessage(null,
group, "image/jpeg", new byte[1000]);
db.receiveMessage(contactId, m);
// Insert a long text message
StringBuilder s = new StringBuilder();
for(int i = 0; i < 100; i++)
s.append("This is a very tedious message. ");
String body = s.toString();
m = messageFactory.createAnonymousMessage(m.getId(),
group1, "text/plain", body.getBytes("UTF-8"));
db.addLocalGroupMessage(m);
// Insert some text messages to the restricted group
for(int i = 0; i < 20; i++) {
if(i % 3 == 0) {
body = "Message " + i + " is short.";
} else {
body = "Message " + i + " is long enough to wrap"
+ " onto a second line on some screens.";
}
now = System.currentTimeMillis();
if(i % 5 == 0) {
m = messageFactory.createAnonymousMessage(null,
group2, privateKey, "text/plain",
body.getBytes("UTF-8"));
} else if(i % 5 == 2) {
m = messageFactory.createPseudonymousMessage(null,
group2, privateKey, author, privateKey,
"text/plain", body.getBytes("UTF-8"));
} else {
m = messageFactory.createPseudonymousMessage(null,
group2, privateKey, author1, privateKey,
"text/plain", body.getBytes("UTF-8"));
}
duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) {
LOG.info("Message creation took " +
duration + " ms");
}
now = System.currentTimeMillis();
if(Math.random() < 0.5) db.addLocalGroupMessage(m);
else db.receiveMessage(contactId, m);
db.setReadFlag(m.getId(), i % 4 == 0);
duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO)) {
LOG.info("Message storage took " +
duration + " ms");
}
}
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} catch(InterruptedException e) {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while waiting for service");
Thread.currentThread().interrupt();
} catch(IOException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
@Override

View File

@@ -4,8 +4,8 @@ import java.util.Collections;
import java.util.List;
import net.sf.briar.android.DescendingHeaderComparator;
import net.sf.briar.api.Author;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;

View File

@@ -9,9 +9,9 @@ import static android.widget.LinearLayout.VERTICAL;
import static java.text.DateFormat.SHORT;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.BAD;
import static net.sf.briar.api.Rating.GOOD;
import static net.sf.briar.api.Rating.UNRATED;
import static net.sf.briar.api.messaging.Rating.BAD;
import static net.sf.briar.api.messaging.Rating.GOOD;
import static net.sf.briar.api.messaging.Rating.UNRATED;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.Executor;
@@ -24,15 +24,15 @@ import net.sf.briar.android.BriarService.BriarServiceConnection;
import net.sf.briar.android.widgets.CommonLayoutParams;
import net.sf.briar.android.widgets.HorizontalBorder;
import net.sf.briar.android.widgets.HorizontalSpace;
import net.sf.briar.api.Rating;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.android.BundleEncrypter;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.NoSuchMessageException;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Rating;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;

View File

@@ -4,7 +4,6 @@ import static android.content.Context.MODE_PRIVATE;
import java.io.File;
import net.sf.briar.api.crypto.Password;
import net.sf.briar.api.db.DatabaseConfig;
import net.sf.briar.api.ui.UiCallback;
import android.app.Application;
@@ -39,13 +38,8 @@ public class HelloWorldModule extends AbstractModule {
return app.getApplicationContext().getDir("db", MODE_PRIVATE);
}
public Password getPassword() {
return new Password() {
public char[] getPassword() {
return "foo bar".toCharArray();
}
};
public char[] getPassword() {
return "foo bar".toCharArray();
}
public long getMaxSize() {

View File

@@ -1,25 +1,14 @@
package net.sf.briar.android.invitation;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.android.BriarActivity;
import net.sf.briar.android.BriarService;
import net.sf.briar.android.BriarService.BriarServiceConnection;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.android.BundleEncrypter;
import net.sf.briar.api.android.ReferenceManager;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.invitation.InvitationListener;
import net.sf.briar.api.invitation.InvitationState;
import net.sf.briar.api.invitation.InvitationTask;
import net.sf.briar.api.invitation.InvitationTaskFactory;
import android.content.Intent;
import android.os.Bundle;
import com.google.inject.Inject;
@@ -27,12 +16,6 @@ import com.google.inject.Inject;
public class AddContactActivity extends BriarActivity
implements InvitationListener {
private static final Logger LOG =
Logger.getLogger(AddContactActivity.class.getName());
private final BriarServiceConnection serviceConnection =
new BriarServiceConnection();
@Inject private BundleEncrypter bundleEncrypter;
@Inject private CryptoComponent crypto;
@Inject private InvitationTaskFactory invitationTaskFactory;
@@ -40,6 +23,7 @@ implements InvitationListener {
private AddContactView view = null;
private InvitationTask task = null;
private long taskHandle = -1;
private AuthorId localAuthorId = null;
private String networkName = null;
private boolean useBluetooth = false;
private int localInvitationCode = -1, remoteInvitationCode = -1;
@@ -47,10 +31,7 @@ implements InvitationListener {
private boolean connectionFailed = false;
private boolean localCompared = false, remoteCompared = false;
private boolean localMatched = false, remoteMatched = false;
// Fields that are accessed from DB threads must be volatile
@Inject private volatile DatabaseComponent db;
@Inject @DatabaseExecutor private volatile Executor dbExecutor;
private String contactName = null;
@Override
public void onCreate(Bundle state) {
@@ -60,6 +41,8 @@ implements InvitationListener {
setView(new NetworkSetupView(this));
} else {
// Restore the activity's state
byte[] id = state.getByteArray("net.sf.briar.LOCAL_AUTHOR_ID");
if(id != null) localAuthorId = new AuthorId(id);
networkName = state.getString("net.sf.briar.NETWORK_NAME");
useBluetooth = state.getBoolean("net.sf.briar.USE_BLUETOOTH");
taskHandle = state.getLong("net.sf.briar.TASK_HANDLE", -1);
@@ -70,7 +53,8 @@ implements InvitationListener {
localInvitationCode = state.getInt("net.sf.briar.LOCAL_CODE");
remoteInvitationCode = state.getInt("net.sf.briar.REMOTE_CODE");
connectionFailed = state.getBoolean("net.sf.briar.FAILED");
if(state.getBoolean("net.sf.briar.MATCHED")) {
contactName = state.getString("net.sf.briar.CONTACT_NAME");
if(contactName != null) {
localCompared = remoteCompared = true;
localMatched = remoteMatched = true;
}
@@ -81,10 +65,10 @@ implements InvitationListener {
setView(new InvitationCodeView(this));
} else if(connectionFailed) {
setView(new ConnectionFailedView(this));
} else if(localMatched && remoteMatched) {
setView(new ContactAddedView(this));
} else {
} else if(contactName == null) {
setView(new CodesDoNotMatchView(this));
} else {
setView(new ContactAddedView(this));
}
} else {
// A background task exists - listen to it and get its state
@@ -98,6 +82,7 @@ implements InvitationListener {
remoteCompared = s.getRemoteCompared();
localMatched = s.getLocalMatched();
remoteMatched = s.getRemoteMatched();
contactName = s.getContactName();
// Set the appropriate view for the state
if(localInvitationCode == -1) {
setView(new NetworkSetupView(this));
@@ -112,15 +97,14 @@ implements InvitationListener {
} else if(!remoteCompared) {
setView(new WaitForContactView(this));
} else if(localMatched && remoteMatched) {
setView(new ContactAddedView(this));
if(contactName == null)
setView(new WaitForContactView(this));
else setView(new ContactAddedView(this));
} else {
setView(new CodesDoNotMatchView(this));
}
}
}
// Bind to the service so we can wait for the DB to be opened
bindService(new Intent(BriarService.class.getName()),
serviceConnection, 0);
}
@Override
@@ -131,12 +115,16 @@ implements InvitationListener {
@Override
public void onSaveInstanceState(Bundle state) {
if(localAuthorId != null) {
state.putByteArray("net.sf.briar.LOCAL_AUTHOR_ID",
localAuthorId.getBytes());
}
state.putString("net.sf.briar.NETWORK_NAME", networkName);
state.putBoolean("net.sf.briar.USE_BLUETOOTH", useBluetooth);
state.putInt("net.sf.briar.LOCAL_CODE", localInvitationCode);
state.putInt("net.sf.briar.REMOTE_CODE", remoteInvitationCode);
state.putBoolean("net.sf.briar.FAILED", connectionFailed);
state.putBoolean("net.sf.briar.MATCHED", localMatched && remoteMatched);
state.putString("net.sf.briar.CONTACT_NAME", contactName);
if(task != null) state.putLong("net.sf.briar.TASK_HANDLE", taskHandle);
bundleEncrypter.encrypt(state);
}
@@ -145,7 +133,6 @@ implements InvitationListener {
public void onDestroy() {
super.onDestroy();
if(task != null) task.removeListener(this);
unbindService(serviceConnection);
}
void setView(AddContactView view) {
@@ -157,6 +144,7 @@ implements InvitationListener {
void reset(AddContactView view) {
task = null;
taskHandle = -1;
localAuthorId = null;
networkName = null;
useBluetooth = false;
localInvitationCode = -1;
@@ -164,9 +152,14 @@ implements InvitationListener {
connectionFailed = false;
localCompared = remoteCompared = false;
localMatched = remoteMatched = false;
contactName = null;
setView(view);
}
void setLocalAuthorId(AuthorId localAuthorId) {
this.localAuthorId = localAuthorId;
}
void setNetworkName(String networkName) {
this.networkName = networkName;
}
@@ -191,8 +184,10 @@ implements InvitationListener {
void remoteInvitationCodeEntered(int code) {
setView(new ConnectionView(this));
// FIXME: These calls are blocking the UI thread for too long
task = invitationTaskFactory.createTask(localInvitationCode, code);
if(localAuthorId == null) throw new IllegalStateException();
if(localInvitationCode == -1) throw new IllegalStateException();
task = invitationTaskFactory.createTask(localAuthorId,
localInvitationCode, code);
taskHandle = referenceManager.putReference(task, InvitationTask.class);
task.addListener(AddContactActivity.this);
task.addListener(new ReferenceCleaner(referenceManager, taskHandle));
@@ -204,11 +199,10 @@ implements InvitationListener {
}
void remoteConfirmationCodeEntered(int code) {
localCompared = true;
if(code == remoteConfirmationCode) {
localMatched = true;
if(remoteMatched) setView(new ContactAddedView(this));
else if(remoteCompared) setView(new CodesDoNotMatchView(this));
else setView(new WaitForContactView(this));
setView(new WaitForContactView(this));
task.localConfirmationSucceeded();
} else {
setView(new CodesDoNotMatchView(this));
@@ -216,23 +210,8 @@ implements InvitationListener {
}
}
void addContactAndFinish(final String nickname) {
dbExecutor.execute(new Runnable() {
public void run() {
try {
serviceConnection.waitForStartup();
db.addContact(nickname);
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} catch(InterruptedException e) {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while waiting for service");
Thread.currentThread().interrupt();
}
}
});
finish();
String getContactName() {
return contactName;
}
public void connectionSucceeded(final int localCode, final int remoteCode) {
@@ -259,8 +238,6 @@ implements InvitationListener {
public void run() {
remoteCompared = true;
remoteMatched = true;
if(localMatched)
setView(new ContactAddedView(AddContactActivity.this));
}
});
}
@@ -275,6 +252,23 @@ implements InvitationListener {
});
}
public void pseudonymExchangeSucceeded(final String remoteName) {
runOnUiThread(new Runnable() {
public void run() {
contactName = remoteName;
setView(new ContactAddedView(AddContactActivity.this));
}
});
}
public void pseudonymExchangeFailed() {
runOnUiThread(new Runnable() {
public void run() {
setView(new ConnectionFailedView(AddContactActivity.this));
}
});
}
/**
* Cleans up the reference to the invitation task when the task completes.
* This class is static to prevent memory leaks.
@@ -299,11 +293,19 @@ implements InvitationListener {
}
public void remoteConfirmationSucceeded() {
referenceManager.removeReference(handle, InvitationTask.class);
// Wait for the pseudonym exchange to succeed or fail
}
public void remoteConfirmationFailed() {
referenceManager.removeReference(handle, InvitationTask.class);
}
public void pseudonymExchangeSucceeded(String remoteName) {
referenceManager.removeReference(handle, InvitationTask.class);
}
public void pseudonymExchangeFailed() {
referenceManager.removeReference(handle, InvitationTask.class);
}
}
}

View File

@@ -1,27 +1,18 @@
package net.sf.briar.android.invitation;
import static android.text.InputType.TYPE_CLASS_TEXT;
import static android.text.InputType.TYPE_TEXT_FLAG_CAP_WORDS;
import static android.text.InputType.TYPE_TEXT_VARIATION_PERSON_NAME;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import net.sf.briar.R;
import net.sf.briar.android.widgets.CommonLayoutParams;
import android.content.Context;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
public class ContactAddedView extends AddContactView implements OnClickListener,
OnEditorActionListener {
EditText nicknameEntry = null;
public class ContactAddedView extends AddContactView
implements OnClickListener {
ContactAddedView(Context ctx) {
super(ctx);
@@ -46,50 +37,21 @@ OnEditorActionListener {
innerLayout.addView(added);
addView(innerLayout);
TextView enterNickname = new TextView(ctx);
enterNickname.setGravity(CENTER_HORIZONTAL);
enterNickname.setPadding(10, 0, 10, 10);
enterNickname.setText(R.string.enter_nickname);
addView(enterNickname);
TextView contactName = new TextView(ctx);
contactName.setTextSize(22);
contactName.setPadding(10, 0, 10, 10);
contactName.setText(container.getContactName());
addView(contactName);
innerLayout = new LinearLayout(ctx);
innerLayout.setOrientation(HORIZONTAL);
innerLayout.setGravity(CENTER);
final Button doneButton = new Button(ctx);
Button doneButton = new Button(ctx);
doneButton.setLayoutParams(CommonLayoutParams.WRAP_WRAP);
doneButton.setText(R.string.done_button);
doneButton.setEnabled(false);
doneButton.setOnClickListener(this);
nicknameEntry = new EditText(ctx) {
@Override
protected void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
doneButton.setEnabled(text.length() > 0);
}
};
nicknameEntry.setTextSize(26);
nicknameEntry.setPadding(10, 0, 10, 10);
nicknameEntry.setMinEms(5);
nicknameEntry.setMaxEms(20);
nicknameEntry.setMaxLines(1);
nicknameEntry.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_WORDS |
TYPE_TEXT_VARIATION_PERSON_NAME);
nicknameEntry.setOnEditorActionListener(this);
innerLayout.addView(nicknameEntry);
innerLayout.addView(doneButton);
addView(innerLayout);
}
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
String nickname = textView.getText().toString();
if(nickname.length() > 0) container.addContactAndFinish(nickname);
return true;
addView(doneButton);
}
public void onClick(View view) {
String nickname = nicknameEntry.getText().toString();
container.addContactAndFinish(nickname);
container.finish();
}
}

View File

@@ -20,11 +20,13 @@ implements WifiStateListener, BluetoothStateListener, OnClickListener {
void populate() {
removeAllViews();
Context ctx = getContext();
TextView sameNetwork = new TextView(ctx);
sameNetwork.setTextSize(14);
sameNetwork.setPadding(10, 10, 10, 10);
sameNetwork.setText(R.string.same_network);
addView(sameNetwork);
TextView chooseIdentity = new TextView(ctx);
chooseIdentity.setTextSize(14);
chooseIdentity.setPadding(10, 10, 10, 10);
chooseIdentity.setText(R.string.choose_identity);
addView(chooseIdentity);
// FIXME: Add a spinner for choosing which identity to use
WifiWidget wifi = new WifiWidget(ctx);
wifi.init(this);

View File

@@ -10,7 +10,7 @@ import android.widget.ArrayAdapter;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
class ContactNameSpinnerAdapter extends ArrayAdapter<Contact>
public class ContactNameSpinnerAdapter extends ArrayAdapter<Contact>
implements SpinnerAdapter {
ContactNameSpinnerAdapter(Context context) {
@@ -24,7 +24,7 @@ implements SpinnerAdapter {
name.setTextSize(18);
name.setMaxLines(1);
name.setPadding(10, 10, 10, 10);
name.setText(getItem(position).getName());
name.setText(getItem(position).getAuthor().getName());
return name;
}

View File

@@ -5,8 +5,6 @@ import static android.widget.LinearLayout.VERTICAL;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
@@ -25,7 +23,6 @@ import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.PrivateMessageHeader;
@@ -34,8 +31,6 @@ import net.sf.briar.api.db.event.DatabaseEvent;
import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.db.event.MessageExpiredEvent;
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageFactory;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
@@ -60,9 +55,7 @@ implements OnClickListener, DatabaseListener {
// Fields that are accessed from DB threads must be volatile
@Inject private volatile DatabaseComponent db;
@Inject @DatabaseExecutor private volatile Executor dbExecutor;
@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
@Inject private volatile MessageFactory messageFactory;
@Override
public void onCreate(Bundle state) {
@@ -93,70 +86,6 @@ implements OnClickListener, DatabaseListener {
// Bind to the service so we can wait for the DB to be opened
bindService(new Intent(BriarService.class.getName()),
serviceConnection, 0);
// Add some fake messages to the database in a background thread
insertFakeMessages();
}
// FIXME: Remove this
private void insertFakeMessages() {
dbExecutor.execute(new Runnable() {
public void run() {
try {
// Wait for the service to be bound and started
serviceConnection.waitForStartup();
// If there are no messages in the DB, create some fake ones
Collection<PrivateMessageHeader> headers =
db.getPrivateMessageHeaders();
if(!headers.isEmpty()) return;
if(LOG.isLoggable(INFO))
LOG.info("Inserting fake private messages");
// We'll also need a contact to exchange messages with
ContactId contactId = db.addContact("Carol");
// Insert some text messages to and from the contact
for(int i = 0; i < 20; i++) {
String body;
if(i % 3 == 0) {
body = "Message " + i + " is short.";
} else {
body = "Message " + i + " is long enough to"
+ " wrap onto a second line on some"
+ " screens.";
}
Message m = messageFactory.createPrivateMessage(null,
"text/plain", body.getBytes("UTF-8"));
if(Math.random() < 0.5)
db.addLocalPrivateMessage(m, contactId);
else db.receiveMessage(contactId, m);
db.setReadFlag(m.getId(), i % 4 == 0);
}
// Insert a non-text message
Message m = messageFactory.createPrivateMessage(null,
"image/jpeg", new byte[1000]);
db.receiveMessage(contactId, m);
// Insert a long text message
StringBuilder s = new StringBuilder();
for(int i = 0; i < 100; i++)
s.append("This is a very tedious message. ");
m = messageFactory.createPrivateMessage(m.getId(),
"text/plain", s.toString().getBytes("UTF-8"));
db.addLocalPrivateMessage(m, contactId);
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} catch(InterruptedException e) {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while waiting for service");
Thread.currentThread().interrupt();
} catch(IOException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
@Override

View File

@@ -31,7 +31,7 @@ class ConversationListItem {
}
String getContactName() {
return contact.getName();
return contact.getAuthor().getName();
}
String getSubject() {

View File

@@ -1,6 +1,6 @@
package net.sf.briar.api.messaging;
package net.sf.briar.api;
/** A pseudonymous author of {@link Message}s. */
/** A pseudonym for a user. */
public class Author {
private final AuthorId id;
@@ -23,10 +23,7 @@ public class Author {
return name;
}
/**
* Returns the public key that is used to verify messages signed by the
* author.
*/
/** Returns the public key used to verify the pseudonym's signatures. */
public byte[] getPublicKey() {
return publicKey;
}

View File

@@ -1,4 +1,4 @@
package net.sf.briar.api.messaging;
package net.sf.briar.api;
import java.io.IOException;

View File

@@ -1,4 +1,4 @@
package net.sf.briar.api.messaging;
package net.sf.briar.api;
import java.util.Arrays;

View File

@@ -3,12 +3,12 @@ package net.sf.briar.api;
public class Contact {
private final ContactId id;
private final String name;
private final Author author;
private final long lastConnected;
public Contact(ContactId id, String name, long lastConnected) {
public Contact(ContactId id, Author author, long lastConnected) {
this.id = id;
this.name = name;
this.author = author;
this.lastConnected = lastConnected;
}
@@ -16,8 +16,8 @@ public class Contact {
return id;
}
public String getName() {
return name;
public Author getAuthor() {
return author;
}
public long getLastConnected() {

View File

@@ -1,6 +1,6 @@
package net.sf.briar.api.messaging;
package net.sf.briar.api;
/** A pseudonym that the user can use to sign {@link Message}s. */
/** A pseudonym for the local user. */
public class LocalAuthor extends Author {
private final byte[] privateKey;
@@ -11,7 +11,7 @@ public class LocalAuthor extends Author {
this.privateKey = privateKey;
}
/** Returns the private key that is used to sign messages. */
/** Returns the private key used to generate the pseudonym's signatures. */
public byte[] getPrivateKey() {
return privateKey;
}

View File

@@ -1,4 +1,4 @@
package net.sf.briar.api.messaging;
package net.sf.briar.api;
import java.util.Arrays;

View File

@@ -1,4 +1,4 @@
package net.sf.briar.api.messaging;
package net.sf.briar.api;
import java.util.Arrays;

View File

@@ -9,6 +9,61 @@ import javax.crypto.Cipher;
public interface CryptoComponent {
ErasableKey generateSecretKey();
MessageDigest getMessageDigest();
PseudoRandom getPseudoRandom(int seed1, int seed2);
SecureRandom getSecureRandom();
Signature getSignature();
KeyPair generateAgreementKeyPair();
KeyParser getAgreementKeyParser();
KeyPair generateSignatureKeyPair();
KeyParser getSignatureKeyParser();
/** Generates a random invitation code. */
int generateInvitationCode();
/**
* Derives two confirmation codes from the given master secret. The first
* code is for Alice to give to Bob; the second is for Bob to give to
* Alice.
*/
int[] deriveConfirmationCodes(byte[] secret);
/**
* Derives two nonces from the given master secret. The first nonce is for
* Alice to sign; the second is for Bob to sign.
*/
byte[][] deriveInvitationNonces(byte[] secret);
/**
* Derives a shared master secret from two public keys and one of the
* corresponding private keys.
* @param alice indicates whether the private key belongs to Alice or Bob.
*/
byte[] deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
boolean alice) throws GeneralSecurityException;
/**
* Derives an initial secret for the given transport from the given master
* secret.
*/
byte[] deriveInitialSecret(byte[] secret, int transportIndex);
/**
* Derives a temporary secret for the given period from the given secret,
* which is either the initial shared secret or the previous period's
* temporary secret.
*/
byte[] deriveNextSecret(byte[] secret, long period);
/**
* Derives a tag key from the given temporary secret.
* @param alice indicates whether the key is for connections initiated by
@@ -28,57 +83,18 @@ public interface CryptoComponent {
boolean initiator);
/**
* Derives an initial shared secret from two public keys and one of the
* corresponding private keys.
* @param alice indicates whether the private key belongs to Alice or Bob.
* Returns a cipher for generating the pseudo-random tags that are used to
* recognise connections.
*/
byte[] deriveInitialSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
boolean alice) throws GeneralSecurityException;
Cipher getTagCipher();
/**
* Generates a random invitation code.
*/
int generateInvitationCode();
/**
* Derives two confirmation codes from the given initial shared secret. The
* first code is for Alice to give to Bob; the second is for Bob to give to
* Alice.
*/
int[] deriveConfirmationCodes(byte[] secret);
/**
* Derives a temporary secret for the given period from the previous
* period's temporary secret.
*/
byte[] deriveNextSecret(byte[] secret, long period);
/** Returns a cipher for encrypting and authenticating connections. */
AuthenticatedCipher getFrameCipher();
/** Encodes the pseudo-random tag that is used to recognise a connection. */
void encodeTag(byte[] tag, Cipher tagCipher, ErasableKey tagKey,
long connection);
KeyPair generateAgreementKeyPair();
KeyParser getAgreementKeyParser();
KeyPair generateSignatureKeyPair();
KeyParser getSignatureKeyParser();
ErasableKey generateSecretKey();
MessageDigest getMessageDigest();
PseudoRandom getPseudoRandom(int seed1, int seed2);
SecureRandom getSecureRandom();
Cipher getTagCipher();
AuthenticatedCipher getFrameCipher();
Signature getSignature();
/**
* Encrypts the given plaintext so it can be written to temporary storage.
* The ciphertext will not be decryptable after the app restarts.

View File

@@ -1,7 +1,7 @@
package net.sf.briar.api.crypto;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.Endpoint;

View File

@@ -1,9 +1,13 @@
package net.sf.briar.api.crypto;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
public interface KeyParser {
PublicKey parsePublicKey(byte[] encodedKey) throws InvalidKeySpecException;
PrivateKey parsePrivateKey(byte[] encodedKey)
throws InvalidKeySpecException;
}

View File

@@ -1,14 +0,0 @@
package net.sf.briar.api.crypto;
/**
* Encapsulates a password. Implementations may keep the password encrypted in
* memory to reduce the chances of writing it to the swapfile in plaintext.
*/
public interface Password {
/**
* Returns the password as a character array, which should be filled with
* zeroes as soon as it has been used.
*/
char[] getPassword();
}

View File

@@ -1,5 +1,6 @@
package net.sf.briar.api.crypto;
/** A deterministic PRNG. */
public interface PseudoRandom {
byte[] nextBytes(int bytes);

View File

@@ -0,0 +1,10 @@
package net.sf.briar.api.db;
/**
* Thrown when a duplicate contact is added to the database. This exception may
* occur due to concurrent updates and does not indicate a database error.
*/
public class ContactExistsException extends DbException {
private static final long serialVersionUID = -6658762011691502411L;
}

View File

@@ -4,28 +4,29 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.messaging.Ack;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.LocalAuthor;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Offer;
import net.sf.briar.api.messaging.Rating;
import net.sf.briar.api.messaging.Request;
import net.sf.briar.api.messaging.RetentionAck;
import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.transport.Endpoint;
import net.sf.briar.api.transport.TemporarySecret;
@@ -53,9 +54,10 @@ public interface DatabaseComponent {
void removeListener(DatabaseListener d);
/**
* Stores a contact with the given name and returns an ID for the contact.
* Stores a contact with the given pseudonym, associated with the given
* local pseudonym, and returns an ID for the contact.
*/
ContactId addContact(String name) throws DbException;
ContactId addContact(Author remote, AuthorId local) throws DbException;
/** Stores an endpoint. */
void addEndpoint(Endpoint ep) throws DbException;
@@ -85,7 +87,7 @@ public interface DatabaseComponent {
* Stores a transport and returns true if the transport was not previously
* in the database.
*/
boolean addTransport(TransportId t) throws DbException;
boolean addTransport(TransportId t, long maxLatency) throws DbException;
/**
* Generates an acknowledgement for the given contact, or returns null if
@@ -176,12 +178,19 @@ public interface DatabaseComponent {
/** Returns the group with the given ID, if the user subscribes to it. */
Group getGroup(GroupId g) throws DbException;
/** Returns the pseudonym with the given ID. */
LocalAuthor getLocalAuthor(AuthorId a) throws DbException;
/** Returns all pseudonyms that the user can use to sign messages. */
Collection<LocalAuthor> getLocalAuthors() throws DbException;
/** Returns all restricted groups to which the user can post messages. */
Collection<LocalGroup> getLocalGroups() throws DbException;
/** Returns the local transport properties for all transports. */
Map<TransportId, TransportProperties> getLocalProperties()
throws DbException;
/** Returns the local transport properties for the given transport. */
TransportProperties getLocalProperties(TransportId t) throws DbException;
@@ -222,6 +231,9 @@ public interface DatabaseComponent {
/** Returns the set of groups to which the user subscribes. */
Collection<Group> getSubscriptions() throws DbException;
/** Returns the maximum latencies of all local transports. */
Map<TransportId, Long> getTransportLatencies() throws DbException;
/** Returns the number of unread messages in each subscribed group. */
Map<GroupId, Integer> getUnreadMessageCounts() throws DbException;
@@ -317,6 +329,13 @@ public interface DatabaseComponent {
*/
boolean setReadFlag(MessageId m, boolean read) throws DbException;
/**
* Sets the remote transport properties for the given contact, replacing
* any existing properties.
*/
void setRemoteProperties(ContactId c,
Map<TransportId, TransportProperties> p) throws DbException;
/** Records the given messages as having been seen by the given contact. */
void setSeen(ContactId c, Collection<MessageId> seen) throws DbException;

View File

@@ -2,13 +2,11 @@ package net.sf.briar.api.db;
import java.io.File;
import net.sf.briar.api.crypto.Password;
public interface DatabaseConfig {
File getDataDirectory();
Password getPassword();
char[] getPassword();
long getMaxSize();
}

View File

@@ -1,9 +1,9 @@
package net.sf.briar.api.db;
import net.sf.briar.api.Rating;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.Author;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Rating;
public class GroupMessageHeader extends MessageHeader {

View File

@@ -1,7 +1,7 @@
package net.sf.briar.api.db.event;
import net.sf.briar.api.Rating;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.messaging.Rating;
public class RatingChangedEvent extends DatabaseEvent {

View File

@@ -1,7 +1,7 @@
package net.sf.briar.api.db.event;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
/**
* An event that is broadcast when a contact's remote transport properties

View File

@@ -1,17 +1,23 @@
package net.sf.briar.api.db.event;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
/** An event that is broadcast when a transport is added. */
/** An event that is broadcast when a transport is added to the database. */
public class TransportAddedEvent extends DatabaseEvent {
private final TransportId transportId;
private final long maxLatency;
public TransportAddedEvent(TransportId transportId) {
public TransportAddedEvent(TransportId transportId, long maxLatency) {
this.transportId = transportId;
this.maxLatency = maxLatency;
}
public TransportId getTransportId() {
return transportId;
}
public long getMaxLatency() {
return maxLatency;
}
}

View File

@@ -1,6 +1,6 @@
package net.sf.briar.api.db.event;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
/** An event that is broadcast when a transport is removed. */
public class TransportRemovedEvent extends DatabaseEvent {

View File

@@ -9,7 +9,10 @@ public interface InvitationListener {
/** Called if a connection is established and key agreement succeeds. */
void connectionSucceeded(int localCode, int remoteCode);
/** Called if a connection cannot be established. */
/**
* Called if a connection cannot be established. This indicates that the
* protocol has ended unsuccessfully.
*/
void connectionFailed();
/**
@@ -20,7 +23,21 @@ public interface InvitationListener {
/**
* Informs the local peer that the remote peer's confirmation check did
* not succeed, or the connection was lost during confirmation.
* not succeed, or the connection was lost during confirmation. This
* indicates that the protocol has ended unsuccessfully.
*/
void remoteConfirmationFailed();
/**
* Informs the local peer of the name used by the remote peer. Called if
* the exchange of pseudonyms succeeds. This indicates that the protocol
* has ended successfully.
*/
void pseudonymExchangeSucceeded(String remoteName);
/**
* Called if the exchange of pseudonyms fails. This indicates that the
* protocol has ended unsuccessfully.
*/
void pseudonymExchangeFailed();
}

View File

@@ -7,12 +7,13 @@ public class InvitationState {
private final boolean connectionFailed;
private final boolean localCompared, remoteCompared;
private final boolean localMatched, remoteMatched;
private final String contactName;
public InvitationState(int localInvitationCode, int remoteInvitationCode,
int localConfirmationCode, int remoteConfirmationCode,
boolean connectionFailed, boolean localCompared,
boolean remoteCompared, boolean localMatched,
boolean remoteMatched) {
boolean remoteMatched, String contactName) {
this.localInvitationCode = localInvitationCode;
this.remoteInvitationCode = remoteInvitationCode;
this.localConfirmationCode = localConfirmationCode;
@@ -22,6 +23,7 @@ public class InvitationState {
this.remoteCompared = remoteCompared;
this.localMatched = localMatched;
this.remoteMatched = remoteMatched;
this.contactName = contactName;
}
public int getLocalInvitationCode() {
@@ -59,4 +61,8 @@ public class InvitationState {
public boolean getRemoteMatched() {
return remoteMatched;
}
public String getContactName() {
return contactName;
}
}

View File

@@ -1,8 +1,11 @@
package net.sf.briar.api.invitation;
import net.sf.briar.api.AuthorId;
/** Creates tasks for exchanging invitations with remote peers. */
public interface InvitationTaskFactory {
/** Creates a task using the given invitation codes. */
InvitationTask createTask(int localCode, int remoteCode);
/** Creates a task using the given pseudonym and invitation codes. */
InvitationTask createTask(AuthorId localAuthorId, int localCode,
int remoteCode);
}

View File

@@ -29,8 +29,9 @@ public class Group {
}
/**
* If the group is restricted, returns the public key that is used to
* authorise all messages sent to the group. Otherwise returns null.
* If the group is restricted, returns the public key used to verify the
* signatures on all messages sent to the group. If the group is
* unrestricted, returns null.
*/
public byte[] getPublicKey() {
return publicKey;

View File

@@ -2,6 +2,8 @@ package net.sf.briar.api.messaging;
import java.util.Arrays;
import net.sf.briar.api.UniqueId;
/**
* Type-safe wrapper for a byte array that uniquely identifies a {@link Group}.
*/

View File

@@ -1,6 +1,6 @@
package net.sf.briar.api.messaging;
/** A restricted group to which the user can post messages. */
/** A restricted group to which the local user can post messages. */
public class LocalGroup extends Group {
private final byte[] privateKey;
@@ -11,7 +11,7 @@ public class LocalGroup extends Group {
this.privateKey = privateKey;
}
/** Returns the private key that is used to sign messages. */
/** Returns the private key used to sign all messages sent to the group. */
public byte[] getPrivateKey() {
return privateKey;
}

View File

@@ -1,5 +1,7 @@
package net.sf.briar.api.messaging;
import net.sf.briar.api.Author;
public interface Message {
/** Returns the message's unique identifier. */
@@ -18,8 +20,8 @@ public interface Message {
Group getGroup();
/**
* Returns the message's {@link Author}, or null if this is an anonymous
* message.
* Returns the message's {@link net.sf.briar.api.Author Author}, or null
* if this is an anonymous message.
*/
Author getAuthor();
@@ -33,7 +35,7 @@ public interface Message {
*/
String getSubject();
/** Returns the timestamp created by the message's {@link Author}. */
/** Returns the message's timestamp. */
long getTimestamp();
/** Returns the serialised message. */

View File

@@ -4,6 +4,8 @@ import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import net.sf.briar.api.Author;
public interface MessageFactory {
/** Creates a private message. */

View File

@@ -2,6 +2,8 @@ package net.sf.briar.api.messaging;
import java.util.Arrays;
import net.sf.briar.api.UniqueId;
/**
* Type-safe wrapper for a byte array that uniquely identifies a
* {@link Message}.

View File

@@ -52,5 +52,5 @@ public interface MessagingConstants {
* The timestamp of the oldest message in the database is rounded using
* this modulus to avoid revealing the presence of any particular message.
*/
long RETENTION_MODULUS = 60 * 60 * 1000; // 1 hour
int RETENTION_MODULUS = 60 * 60 * 1000; // 1 hour
}

View File

@@ -1,4 +1,4 @@
package net.sf.briar.api;
package net.sf.briar.api.messaging;
/** The ratings that may be applied to an author in peer moderation. */
public enum Rating {

View File

@@ -1,5 +1,7 @@
package net.sf.briar.api.messaging;
import net.sf.briar.api.TransportId;
/** A packet acknowledging a {@link TransportUpdate}. */
public class TransportAck {

View File

@@ -1,5 +1,6 @@
package net.sf.briar.api.messaging;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
/**

View File

@@ -1,6 +1,8 @@
package net.sf.briar.api.messaging;
/** A {@link Message} that has not yet had its signatures verified. */
import net.sf.briar.api.Author;
/** A {@link Message} that has not yet had its signatures (if any) verified. */
public class UnverifiedMessage {
private final MessageId parent;
@@ -47,8 +49,8 @@ public class UnverifiedMessage {
}
/**
* Returns the message's {@link Author}, or null if this is an anonymous
* message.
* Returns the message's {@link net.sf.briar.api.Author Author}, or null
* if this is an anonymous message.
*/
public Author getAuthor() {
return author;
@@ -68,7 +70,7 @@ public class UnverifiedMessage {
return subject;
}
/** Returns the timestamp created by the message's {@link Author}. */
/** Returns the message's timestamp. */
public long getTimestamp() {
return timestamp;
}

View File

@@ -1,13 +1,14 @@
package net.sf.briar.api.messaging.duplex;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.transport.ConnectionContext;
public interface DuplexConnectionFactory {
void createIncomingConnection(ConnectionContext ctx, DuplexTransportConnection d);
void createIncomingConnection(ConnectionContext ctx,
DuplexTransportConnection d);
void createOutgoingConnection(ContactId c, TransportId t,
DuplexTransportConnection d);

View File

@@ -1,14 +1,15 @@
package net.sf.briar.api.messaging.simplex;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.plugins.simplex.SimplexTransportReader;
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
import net.sf.briar.api.transport.ConnectionContext;
public interface SimplexConnectionFactory {
void createIncomingConnection(ConnectionContext ctx, SimplexTransportReader r);
void createIncomingConnection(ConnectionContext ctx,
SimplexTransportReader r);
void createOutgoingConnection(ContactId c, TransportId t,
SimplexTransportWriter w);

View File

@@ -4,7 +4,7 @@ import java.io.IOException;
import java.util.Collection;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
public interface Plugin {

View File

@@ -23,6 +23,6 @@ public interface PluginManager {
*/
int stop();
/** Returns any duplex plugins that support invitations. */
/** Returns any running duplex plugins that support invitations. */
Collection<DuplexPlugin> getInvitationPlugins();
}

View File

@@ -1,6 +1,6 @@
package net.sf.briar.api.plugins.duplex;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
public interface DuplexPluginFactory {

View File

@@ -1,6 +1,6 @@
package net.sf.briar.api.plugins.simplex;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
public interface SimplexPluginFactory {

View File

@@ -1,7 +1,7 @@
package net.sf.briar.api.transport;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
public class ConnectionContext {

View File

@@ -1,7 +1,7 @@
package net.sf.briar.api.transport;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.plugins.simplex.SimplexTransportReader;
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;

View File

@@ -4,9 +4,11 @@ import java.io.InputStream;
public interface ConnectionReaderFactory {
/**
* Creates a connection reader for one side of a connection.
*/
/** Creates a connection reader for one side of a connection. */
ConnectionReader createConnectionReader(InputStream in,
ConnectionContext ctx, boolean incoming, boolean initiator);
/** Creates a connection reader for one side of an invitation connection. */
ConnectionReader createInvitationConnectionReader(InputStream in,
byte[] secret, boolean alice);
}

View File

@@ -1,8 +1,8 @@
package net.sf.briar.api.transport;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.messaging.TransportId;
/**
* Maintains the connection reordering windows and decides whether incoming

View File

@@ -3,7 +3,7 @@ package net.sf.briar.api.transport;
import java.util.Collection;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
/**
* Keeps track of which contacts are currently connected by which transports.

View File

@@ -4,9 +4,11 @@ import java.io.OutputStream;
public interface ConnectionWriterFactory {
/**
* Creates a connection writer for one side of a connection.
*/
/** Creates a connection writer for one side of a connection. */
ConnectionWriter createConnectionWriter(OutputStream out, long capacity,
ConnectionContext ctx, boolean incoming, boolean initiator);
/** Creates a connection writer for one side of an invitation connection. */
ConnectionWriter createInvitationConnectionWriter(OutputStream out,
byte[] secret, boolean alice);
}

View File

@@ -1,22 +1,20 @@
package net.sf.briar.api.transport;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
public class Endpoint {
private final ContactId contactId;
private final TransportId transportId;
private final long epoch, clockDiff, latency;
private final long epoch;
private final boolean alice;
public Endpoint(ContactId contactId, TransportId transportId,
long epoch, long clockDiff, long latency, boolean alice) {
public Endpoint(ContactId contactId, TransportId transportId, long epoch,
boolean alice) {
this.contactId = contactId;
this.transportId = transportId;
this.epoch = epoch;
this.clockDiff = clockDiff;
this.latency = latency;
this.alice = alice;
}
@@ -32,14 +30,6 @@ public class Endpoint {
return epoch;
}
public long getClockDifference() {
return clockDiff;
}
public long getLatency() {
return latency;
}
public boolean getAlice() {
return alice;
}

View File

@@ -2,7 +2,7 @@ package net.sf.briar.api.transport;
import static net.sf.briar.api.transport.TransportConstants.CONNECTION_WINDOW_SIZE;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
public class TemporarySecret extends Endpoint {
@@ -11,10 +11,9 @@ public class TemporarySecret extends Endpoint {
/** Creates a temporary secret with the given connection window. */
public TemporarySecret(ContactId contactId, TransportId transportId,
long epoch, long clockDiff, long latency, boolean alice,
long period, byte[] secret, long outgoing, long centre,
byte[] bitmap) {
super(contactId, transportId, epoch, clockDiff, latency, alice);
long epoch, boolean alice, long period, byte[] secret,
long outgoing, long centre, byte[] bitmap) {
super(contactId, transportId, epoch, alice);
this.period = period;
this.secret = secret;
this.outgoing = outgoing;
@@ -24,17 +23,15 @@ public class TemporarySecret extends Endpoint {
/** Creates a temporary secret with a new connection window. */
public TemporarySecret(ContactId contactId, TransportId transportId,
long epoch, long clockDiff, long latency, boolean alice,
long period, byte[] secret) {
this(contactId, transportId, epoch, clockDiff, latency, alice, period,
secret, 0, 0, new byte[CONNECTION_WINDOW_SIZE / 8]);
long epoch, boolean alice, long period, byte[] secret) {
this(contactId, transportId, epoch, alice, period, secret, 0, 0,
new byte[CONNECTION_WINDOW_SIZE / 8]);
}
/** Creates a temporary secret derived from the given endpoint. */
public TemporarySecret(Endpoint ep, long period, byte[] secret) {
this(ep.getContactId(), ep.getTransportId(), ep.getEpoch(),
ep.getClockDifference(), ep.getLatency(), ep.getAlice(),
period, secret);
ep.getAlice(), period, secret);
}
public long getPeriod() {

View File

@@ -27,6 +27,9 @@ public interface TransportConstants {
*/
int MIN_CONNECTION_LENGTH = 1024 * 1024; // 2^20, 1 MiB
/** The maximum difference between two communicating devices' clocks. */
int MAX_CLOCK_DIFFERENCE = 60 * 60 * 1000; // 1 hour
/** The size of the connection reordering window. */
int CONNECTION_WINDOW_SIZE = 32;
}

View File

@@ -11,10 +11,12 @@ import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECField;
import java.security.spec.ECFieldFp;
@@ -39,27 +41,33 @@ import org.spongycastle.crypto.modes.AEADBlockCipher;
import org.spongycastle.crypto.modes.GCMBlockCipher;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import com.google.inject.Inject;
class CryptoComponentImpl implements CryptoComponent {
private static final String PROVIDER = "SC"; // Spongy Castle
private static final String AGREEMENT_KEY_PAIR_ALGO = "ECDH";
private static final int AGREEMENT_KEY_PAIR_BITS = 384;
private static final String AGREEMENT_ALGO = "ECDHC";
private static final String SECRET_KEY_ALGO = "AES";
private static final int SECRET_KEY_BYTES = 32; // 256 bits
private static final String KEY_DERIVATION_ALGO = "AES/CTR/NoPadding";
private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
private static final String DIGEST_ALGO = "SHA-384";
private static final String AGREEMENT_ALGO = "ECDHC";
private static final String AGREEMENT_KEY_PAIR_ALGO = "ECDH";
private static final int AGREEMENT_KEY_PAIR_BITS = 384;
private static final String SIGNATURE_ALGO = "ECDSA";
private static final String SIGNATURE_KEY_PAIR_ALGO = "ECDSA";
private static final int SIGNATURE_KEY_PAIR_BITS = 384;
private static final String SIGNATURE_ALGO = "ECDSA";
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
private static final int GCM_MAC_LENGTH = 16; // 128 bits
private static final String STORAGE_CIPHER_ALGO = "AES/GCM/NoPadding";
private static final int STORAGE_IV_LENGTH = 32; // 256 bits
private static final String KEY_DERIVATION_ALGO = "AES/CTR/NoPadding";
private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
// Labels for secret derivation
private static final byte[] MASTER = { 'M', 'A', 'S', 'T', 'E', 'R', '\0' };
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T', '\0' };
private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E', '\0' };
// Label for confirmation code derivation
private static final byte[] CODE = { 'C', 'O', 'D', 'E', '\0' };
// Label for invitation nonce derivation
private static final byte[] NONCE = { 'N', 'O', 'N', 'C', 'E', '\0' };
// Labels for key derivation
private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G', '\0' };
private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G', '\0' };
@@ -71,11 +79,6 @@ class CryptoComponentImpl implements CryptoComponent {
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '_', 'A', '\0' };
private static final byte[] B_FRAME_B =
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '_', 'B', '\0' };
// Labels for secret derivation
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T', '\0' };
private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E', '\0' };
// Label for confirmation code derivation
private static final byte[] CODE = { 'C', 'O', 'D', 'E', '\0' };
// Blank plaintext for key derivation
private static final byte[] KEY_DERIVATION_BLANK_PLAINTEXT =
new byte[SECRET_KEY_BYTES];
@@ -121,7 +124,6 @@ class CryptoComponentImpl implements CryptoComponent {
private final SecureRandom secureRandom;
private final ErasableKey temporaryStorageKey;
@Inject
CryptoComponentImpl() {
Security.addProvider(new BouncyCastleProvider());
try {
@@ -146,197 +148,6 @@ class CryptoComponentImpl implements CryptoComponent {
temporaryStorageKey = generateSecretKey();
}
public ErasableKey deriveTagKey(byte[] secret, boolean alice) {
if(alice) return deriveKey(secret, A_TAG, 0);
else return deriveKey(secret, B_TAG, 0);
}
public ErasableKey deriveFrameKey(byte[] secret, long connection,
boolean alice, boolean initiator) {
if(alice) {
if(initiator) return deriveKey(secret, A_FRAME_A, connection);
else return deriveKey(secret, A_FRAME_B, connection);
} else {
if(initiator) return deriveKey(secret, B_FRAME_A, connection);
else return deriveKey(secret, B_FRAME_B, connection);
}
}
private ErasableKey deriveKey(byte[] secret, byte[] label, long context) {
byte[] key = counterModeKdf(secret, label, context);
return new ErasableKeyImpl(key, SECRET_KEY_ALGO);
}
// Key derivation function based on a block cipher in CTR mode - see
// NIST SP 800-108, section 5.1
private byte[] counterModeKdf(byte[] secret, byte[] label, long context) {
// The secret must be usable as a key
if(secret.length != SECRET_KEY_BYTES)
throw new IllegalArgumentException();
// The label and context must leave a byte free for the counter
if(label.length + 4 >= KEY_DERIVATION_IV_BYTES)
throw new IllegalArgumentException();
byte[] ivBytes = new byte[KEY_DERIVATION_IV_BYTES];
System.arraycopy(label, 0, ivBytes, 0, label.length);
ByteUtils.writeUint32(context, ivBytes, label.length);
// Use the secret and the IV to encrypt a blank plaintext
IvParameterSpec iv = new IvParameterSpec(ivBytes);
ErasableKey key = new ErasableKeyImpl(secret, SECRET_KEY_ALGO);
try {
Cipher cipher = Cipher.getInstance(KEY_DERIVATION_ALGO, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] output = cipher.doFinal(KEY_DERIVATION_BLANK_PLAINTEXT);
assert output.length == SECRET_KEY_BYTES;
return output;
} catch(GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
public byte[] deriveInitialSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
PublicKey theirPublic = agreementKeyParser.parsePublicKey(
theirPublicKey);
MessageDigest messageDigest = getMessageDigest();
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
byte[] ourHash = messageDigest.digest(ourPublicKey);
byte[] theirHash = messageDigest.digest(theirPublicKey);
byte[] aliceInfo, bobInfo;
if(alice) {
aliceInfo = ourHash;
bobInfo = theirHash;
} else {
aliceInfo = theirHash;
bobInfo = ourHash;
}
// The raw secret comes from the key agreement algorithm
KeyAgreement keyAgreement = KeyAgreement.getInstance(AGREEMENT_ALGO,
PROVIDER);
keyAgreement.init(ourKeyPair.getPrivate());
keyAgreement.doPhase(theirPublic, true);
byte[] rawSecret = keyAgreement.generateSecret();
// Derive the cooked secret from the raw secret using the
// concatenation KDF
byte[] cookedSecret = concatenationKdf(rawSecret, FIRST, aliceInfo,
bobInfo);
ByteUtils.erase(rawSecret);
return cookedSecret;
}
// Key derivation function based on a hash function - see NIST SP 800-56A,
// section 5.8
private byte[] concatenationKdf(byte[] rawSecret, byte[] label,
byte[] initiatorInfo, byte[] responderInfo) {
// The output of the hash function must be long enough to use as a key
MessageDigest messageDigest = getMessageDigest();
if(messageDigest.getDigestLength() < SECRET_KEY_BYTES)
throw new RuntimeException();
// All fields are length-prefixed
byte[] length = new byte[1];
ByteUtils.writeUint8(rawSecret.length, length, 0);
messageDigest.update(length);
messageDigest.update(rawSecret);
ByteUtils.writeUint8(label.length, length, 0);
messageDigest.update(length);
messageDigest.update(label);
ByteUtils.writeUint8(initiatorInfo.length, length, 0);
messageDigest.update(length);
messageDigest.update(initiatorInfo);
ByteUtils.writeUint8(responderInfo.length, length, 0);
messageDigest.update(length);
messageDigest.update(responderInfo);
byte[] hash = messageDigest.digest();
// The secret is the first SECRET_KEY_BYTES bytes of the hash
byte[] output = new byte[SECRET_KEY_BYTES];
System.arraycopy(hash, 0, output, 0, SECRET_KEY_BYTES);
ByteUtils.erase(hash);
return output;
}
public byte[] deriveNextSecret(byte[] secret, long period) {
if(period < 0 || period > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
return counterModeKdf(secret, ROTATE, period);
}
public void encodeTag(byte[] tag, Cipher tagCipher, ErasableKey tagKey,
long connection) {
if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if(connection < 0 || connection > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
for(int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
ByteUtils.writeUint32(connection, tag, 0);
try {
tagCipher.init(ENCRYPT_MODE, tagKey);
int encrypted = tagCipher.doFinal(tag, 0, TAG_LENGTH, tag);
if(encrypted != TAG_LENGTH) throw new IllegalArgumentException();
} catch(GeneralSecurityException e) {
// Unsuitable cipher or key
throw new IllegalArgumentException(e);
}
}
public int generateInvitationCode() {
int codeBytes = (int) Math.ceil(CODE_BITS / 8.0);
byte[] random = new byte[codeBytes];
secureRandom.nextBytes(random);
return ByteUtils.readUint(random, CODE_BITS);
}
public int[] deriveConfirmationCodes(byte[] secret) {
byte[] alice = counterModeKdf(secret, CODE, 0);
byte[] bob = counterModeKdf(secret, CODE, 1);
int[] codes = new int[2];
codes[0] = ByteUtils.readUint(alice, CODE_BITS);
codes[1] = ByteUtils.readUint(bob, CODE_BITS);
ByteUtils.erase(alice);
ByteUtils.erase(bob);
return codes;
}
public KeyPair generateAgreementKeyPair() {
KeyPair keyPair = agreementKeyPairGenerator.generateKeyPair();
// Check that the key pair uses NIST curve P-384
ECPublicKey ecPublicKey = checkP384Params(keyPair.getPublic());
// Return a public key that uses the SEC 1 encoding
ecPublicKey = new Sec1PublicKey(ecPublicKey, AGREEMENT_KEY_PAIR_BITS);
return new KeyPair(ecPublicKey, keyPair.getPrivate());
}
private ECPublicKey checkP384Params(PublicKey publicKey) {
if(!(publicKey instanceof ECPublicKey)) throw new RuntimeException();
ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
ECParameterSpec params = ecPublicKey.getParams();
EllipticCurve curve = params.getCurve();
ECField field = curve.getField();
if(!(field instanceof ECFieldFp)) throw new RuntimeException();
BigInteger q = ((ECFieldFp) field).getP();
if(!q.equals(P_384_Q)) throw new RuntimeException();
if(!curve.getA().equals(P_384_A)) throw new RuntimeException();
if(!curve.getB().equals(P_384_B)) throw new RuntimeException();
if(!params.getGenerator().equals(P_384_G)) throw new RuntimeException();
if(!params.getOrder().equals(P_384_N)) throw new RuntimeException();
if(!(params.getCofactor() == P_384_H)) throw new RuntimeException();
return ecPublicKey;
}
public KeyParser getAgreementKeyParser() {
return agreementKeyParser;
}
public KeyPair generateSignatureKeyPair() {
KeyPair keyPair = signatureKeyPairGenerator.generateKeyPair();
// Check that the key pair uses NIST curve P-384
ECPublicKey ecPublicKey = checkP384Params(keyPair.getPublic());
// Return a public key that uses the SEC 1 encoding
ecPublicKey = new Sec1PublicKey(ecPublicKey, SIGNATURE_KEY_PAIR_BITS);
return new KeyPair(ecPublicKey, keyPair.getPrivate());
}
public KeyParser getSignatureKeyParser() {
return signatureKeyParser;
}
public ErasableKey generateSecretKey() {
byte[] b = new byte[SECRET_KEY_BYTES];
secureRandom.nextBytes(b);
@@ -368,6 +179,127 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
public KeyPair generateAgreementKeyPair() {
KeyPair keyPair = agreementKeyPairGenerator.generateKeyPair();
// Check that the key pair uses NIST curve P-384
ECPublicKey publicKey = checkP384Params(keyPair.getPublic());
// Return a wrapper that uses the SEC 1 encoding
publicKey = new Sec1PublicKey(publicKey, AGREEMENT_KEY_PAIR_BITS);
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
privateKey = new Sec1PrivateKey(privateKey, AGREEMENT_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
}
public KeyParser getAgreementKeyParser() {
return agreementKeyParser;
}
public KeyPair generateSignatureKeyPair() {
KeyPair keyPair = signatureKeyPairGenerator.generateKeyPair();
// Check that the key pair uses NIST curve P-384
ECPublicKey publicKey = checkP384Params(keyPair.getPublic());
// Return a wrapper that uses the SEC 1 encoding
publicKey = new Sec1PublicKey(publicKey, SIGNATURE_KEY_PAIR_BITS);
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
privateKey = new Sec1PrivateKey(privateKey, SIGNATURE_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
}
public KeyParser getSignatureKeyParser() {
return signatureKeyParser;
}
public int generateInvitationCode() {
int codeBytes = (int) Math.ceil(CODE_BITS / 8.0);
byte[] random = new byte[codeBytes];
secureRandom.nextBytes(random);
return ByteUtils.readUint(random, CODE_BITS);
}
public int[] deriveConfirmationCodes(byte[] secret) {
byte[] alice = counterModeKdf(secret, CODE, 0);
byte[] bob = counterModeKdf(secret, CODE, 1);
int[] codes = new int[2];
codes[0] = ByteUtils.readUint(alice, CODE_BITS);
codes[1] = ByteUtils.readUint(bob, CODE_BITS);
ByteUtils.erase(alice);
ByteUtils.erase(bob);
return codes;
}
public byte[][] deriveInvitationNonces(byte[] secret) {
byte[] alice = counterModeKdf(secret, NONCE, 0);
byte[] bob = counterModeKdf(secret, NONCE, 1);
return new byte[][] { alice, bob };
}
public byte[] deriveMasterSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
MessageDigest messageDigest = getMessageDigest();
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
byte[] ourHash = messageDigest.digest(ourPublicKey);
byte[] theirHash = messageDigest.digest(theirPublicKey);
byte[] aliceInfo, bobInfo;
if(alice) {
aliceInfo = ourHash;
bobInfo = theirHash;
} else {
aliceInfo = theirHash;
bobInfo = ourHash;
}
PrivateKey ourPriv = ourKeyPair.getPrivate();
// The raw secret comes from the key agreement algorithm
byte[] raw = deriveSharedSecret(ourPriv, theirPub);
// Derive the cooked secret from the raw secret using the
// concatenation KDF
byte[] cooked = concatenationKdf(raw, MASTER, aliceInfo, bobInfo);
ByteUtils.erase(raw);
return cooked;
}
// Package access for testing
byte[] deriveSharedSecret(PrivateKey priv, PublicKey pub)
throws GeneralSecurityException {
KeyAgreement keyAgreement = KeyAgreement.getInstance(AGREEMENT_ALGO,
PROVIDER);
keyAgreement.init(priv);
keyAgreement.doPhase(pub, true);
return keyAgreement.generateSecret();
}
public byte[] deriveInitialSecret(byte[] secret, int transportIndex) {
if(transportIndex < 0) throw new IllegalArgumentException();
return counterModeKdf(secret, FIRST, transportIndex);
}
public byte[] deriveNextSecret(byte[] secret, long period) {
if(period < 0 || period > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
return counterModeKdf(secret, ROTATE, period);
}
public ErasableKey deriveTagKey(byte[] secret, boolean alice) {
if(alice) return deriveKey(secret, A_TAG, 0);
else return deriveKey(secret, B_TAG, 0);
}
public ErasableKey deriveFrameKey(byte[] secret, long connection,
boolean alice, boolean initiator) {
if(alice) {
if(initiator) return deriveKey(secret, A_FRAME_A, connection);
else return deriveKey(secret, A_FRAME_B, connection);
} else {
if(initiator) return deriveKey(secret, B_FRAME_A, connection);
else return deriveKey(secret, B_FRAME_B, connection);
}
}
private ErasableKey deriveKey(byte[] secret, byte[] label, long context) {
byte[] key = counterModeKdf(secret, label, context);
return new ErasableKeyImpl(key, SECRET_KEY_ALGO);
}
public Cipher getTagCipher() {
try {
return Cipher.getInstance(TAG_CIPHER_ALGO, PROVIDER);
@@ -377,12 +309,28 @@ class CryptoComponentImpl implements CryptoComponent {
}
public AuthenticatedCipher getFrameCipher() {
// This code is specific to BouncyCastle because javax.crypto.Cipher
// This code is specific to Spongy Castle because javax.crypto.Cipher
// doesn't support additional authenticated data until Java 7
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH);
}
public void encodeTag(byte[] tag, Cipher tagCipher, ErasableKey tagKey,
long connection) {
if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if(connection < 0 || connection > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
for(int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
ByteUtils.writeUint32(connection, tag, 0);
try {
tagCipher.init(ENCRYPT_MODE, tagKey);
int encrypted = tagCipher.doFinal(tag, 0, TAG_LENGTH, tag);
if(encrypted != TAG_LENGTH) throw new IllegalArgumentException();
} catch(GeneralSecurityException e) {
throw new IllegalArgumentException(e); // Unsuitable cipher or key
}
}
public byte[] encryptTemporaryStorage(byte[] input) {
// Generate a random IV
byte[] ivBytes = new byte[STORAGE_IV_LENGTH];
@@ -425,4 +373,77 @@ class CryptoComponentImpl implements CryptoComponent {
return null; // Invalid
}
}
private ECPublicKey checkP384Params(PublicKey publicKey) {
if(!(publicKey instanceof ECPublicKey)) throw new RuntimeException();
ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
ECParameterSpec params = ecPublicKey.getParams();
EllipticCurve curve = params.getCurve();
ECField field = curve.getField();
if(!(field instanceof ECFieldFp)) throw new RuntimeException();
BigInteger q = ((ECFieldFp) field).getP();
if(!q.equals(P_384_Q)) throw new RuntimeException();
if(!curve.getA().equals(P_384_A)) throw new RuntimeException();
if(!curve.getB().equals(P_384_B)) throw new RuntimeException();
if(!params.getGenerator().equals(P_384_G)) throw new RuntimeException();
if(!params.getOrder().equals(P_384_N)) throw new RuntimeException();
if(!(params.getCofactor() == P_384_H)) throw new RuntimeException();
return ecPublicKey;
}
// Key derivation function based on a hash function - see NIST SP 800-56A,
// section 5.8
private byte[] concatenationKdf(byte[] rawSecret, byte[] label,
byte[] initiatorInfo, byte[] responderInfo) {
// The output of the hash function must be long enough to use as a key
MessageDigest messageDigest = getMessageDigest();
if(messageDigest.getDigestLength() < SECRET_KEY_BYTES)
throw new RuntimeException();
// All fields are length-prefixed
byte[] length = new byte[1];
ByteUtils.writeUint8(rawSecret.length, length, 0);
messageDigest.update(length);
messageDigest.update(rawSecret);
ByteUtils.writeUint8(label.length, length, 0);
messageDigest.update(length);
messageDigest.update(label);
ByteUtils.writeUint8(initiatorInfo.length, length, 0);
messageDigest.update(length);
messageDigest.update(initiatorInfo);
ByteUtils.writeUint8(responderInfo.length, length, 0);
messageDigest.update(length);
messageDigest.update(responderInfo);
byte[] hash = messageDigest.digest();
// The secret is the first SECRET_KEY_BYTES bytes of the hash
byte[] output = new byte[SECRET_KEY_BYTES];
System.arraycopy(hash, 0, output, 0, SECRET_KEY_BYTES);
ByteUtils.erase(hash);
return output;
}
// Key derivation function based on a block cipher in CTR mode - see
// NIST SP 800-108, section 5.1
private byte[] counterModeKdf(byte[] secret, byte[] label, long context) {
// The secret must be usable as a key
if(secret.length != SECRET_KEY_BYTES)
throw new IllegalArgumentException();
// The label and context must leave a byte free for the counter
if(label.length + 4 >= KEY_DERIVATION_IV_BYTES)
throw new IllegalArgumentException();
byte[] ivBytes = new byte[KEY_DERIVATION_IV_BYTES];
System.arraycopy(label, 0, ivBytes, 0, label.length);
ByteUtils.writeUint32(context, ivBytes, label.length);
// Use the secret and the IV to encrypt a blank plaintext
IvParameterSpec iv = new IvParameterSpec(ivBytes);
ErasableKey key = new ErasableKeyImpl(secret, SECRET_KEY_ALGO);
try {
Cipher cipher = Cipher.getInstance(KEY_DERIVATION_ALGO, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] output = cipher.doFinal(KEY_DERIVATION_BLANK_PLAINTEXT);
assert output.length == SECRET_KEY_BYTES;
return output;
} catch(GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -2,9 +2,13 @@ package net.sf.briar.crypto;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
@@ -20,20 +24,22 @@ class Sec1KeyParser implements KeyParser {
private final KeyFactory keyFactory;
private final ECParameterSpec params;
private final BigInteger modulus;
private final int bytesPerInt, encodedKeyLength;
private final int keyBits, bytesPerInt, publicKeyBytes, privateKeyBytes;
Sec1KeyParser(KeyFactory keyFactory, ECParameterSpec params,
BigInteger modulus, int keyBits) {
this.keyFactory = keyFactory;
this.params = params;
this.modulus = modulus;
this.keyBits = keyBits;
bytesPerInt = (int) Math.ceil(keyBits / 8.0);
encodedKeyLength = 1 + 2 * bytesPerInt;
publicKeyBytes = 1 + 2 * bytesPerInt;
privateKeyBytes = bytesPerInt;
}
public PublicKey parsePublicKey(byte[] encodedKey)
throws InvalidKeySpecException {
if(encodedKey.length != encodedKeyLength)
if(encodedKey.length != publicKeyBytes)
throw new InvalidKeySpecException();
// The first byte must be 0x04
if(encodedKey[0] != 4) throw new InvalidKeySpecException();
@@ -52,9 +58,23 @@ class Sec1KeyParser implements KeyParser {
BigInteger lhs = y.multiply(y).mod(modulus);
BigInteger rhs = x.multiply(x).add(a).multiply(x).add(b).mod(modulus);
if(!lhs.equals(rhs)) throw new InvalidKeySpecException();
// FIXME: Verify that n times the point (x, y) = the point at infinity
// Construct a public key from the point (x, y) and the params
ECPoint pub = new ECPoint(x, y);
ECPublicKeySpec keySpec = new ECPublicKeySpec(pub, params);
return keyFactory.generatePublic(keySpec);
ECPublicKey k = (ECPublicKey) keyFactory.generatePublic(keySpec);
return new Sec1PublicKey(k, keyBits);
}
public PrivateKey parsePrivateKey(byte[] encodedKey)
throws InvalidKeySpecException {
if(encodedKey.length != privateKeyBytes)
throw new InvalidKeySpecException();
BigInteger s = new BigInteger(1, encodedKey); // Positive signum
if(s.compareTo(params.getOrder()) >= 0)
throw new InvalidKeySpecException();
ECPrivateKeySpec keySpec = new ECPrivateKeySpec(s, params);
ECPrivateKey k = (ECPrivateKey) keyFactory.generatePrivate(keySpec);
return new Sec1PrivateKey(k, keyBits);
}
}

View File

@@ -0,0 +1,57 @@
package net.sf.briar.crypto;
import java.math.BigInteger;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.ECParameterSpec;
class Sec1PrivateKey implements ECPrivateKey,
org.spongycastle.jce.interfaces.ECPrivateKey {
private static final long serialVersionUID = -493100835871466670L;
private final ECPrivateKey key;
private final int privateKeyBytes;
Sec1PrivateKey(ECPrivateKey key, int keyBits) {
// Spongy Castle only accepts instances of its own interface, so we
// have to wrap an instance of that interface and delegate to it
if(!(key instanceof org.spongycastle.jce.interfaces.ECPrivateKey))
throw new IllegalArgumentException();
this.key = key;
privateKeyBytes = (int) Math.ceil(keyBits / 8.0);
}
public String getAlgorithm() {
return key.getAlgorithm();
}
public byte[] getEncoded() {
byte[] encodedKey = new byte[privateKeyBytes];
BigInteger s = key.getS();
// Copy up to privateKeyBytes bytes into exactly privateKeyBytes bytes
byte[] sBytes = s.toByteArray();
for(int i = 0; i < sBytes.length && i < privateKeyBytes; i++)
encodedKey[privateKeyBytes - 1 - i] = sBytes[sBytes.length - 1 - i];
return encodedKey;
}
public String getFormat() {
return "SEC1";
}
public ECParameterSpec getParams() {
return key.getParams();
}
public BigInteger getS() {
return key.getS();
}
public org.spongycastle.jce.spec.ECParameterSpec getParameters() {
return ((org.spongycastle.jce.interfaces.ECPrivateKey) key).getParameters();
}
public BigInteger getD() {
return ((org.spongycastle.jce.interfaces.ECPrivateKey) key).getD();
}
}

View File

@@ -10,17 +10,22 @@ import java.security.spec.ECPoint;
* Elliptic Curve Cryptography", section 2.3 (Certicom Corporation, May 2009).
* Point compression is not used.
*/
class Sec1PublicKey implements ECPublicKey {
class Sec1PublicKey implements ECPublicKey,
org.spongycastle.jce.interfaces.ECPublicKey {
private static final long serialVersionUID = -2722797033851423987L;
private final ECPublicKey key;
private final int bytesPerInt, encodedKeyLength;
private final int bytesPerInt, publicKeyBytes;
Sec1PublicKey(ECPublicKey key, int keyBits) {
// Spongy Castle only accepts instances of its own interface, so we
// have to wrap an instance of that interface and delegate to it
if(!(key instanceof org.spongycastle.jce.interfaces.ECPublicKey))
throw new IllegalArgumentException();
this.key = key;
bytesPerInt = (int) Math.ceil(keyBits / 8.0);
encodedKeyLength = 1 + 2 * bytesPerInt;
publicKeyBytes = 1 + 2 * bytesPerInt;
}
public String getAlgorithm() {
@@ -28,9 +33,10 @@ class Sec1PublicKey implements ECPublicKey {
}
public byte[] getEncoded() {
byte[] encodedKey = new byte[encodedKeyLength];
byte[] encodedKey = new byte[publicKeyBytes];
encodedKey[0] = 4;
BigInteger x = key.getW().getAffineX(), y = key.getW().getAffineY();
BigInteger x = key.getW().getAffineX();
BigInteger y = key.getW().getAffineY();
// Copy up to bytesPerInt bytes into exactly bytesPerInt bytes
byte[] xBytes = x.toByteArray();
for(int i = 0; i < xBytes.length && i < bytesPerInt; i++)
@@ -52,4 +58,12 @@ class Sec1PublicKey implements ECPublicKey {
public ECPoint getW() {
return key.getW();
}
public org.spongycastle.jce.spec.ECParameterSpec getParameters() {
return ((org.spongycastle.jce.interfaces.ECPublicKey) key).getParameters();
}
public org.spongycastle.math.ec.ECPoint getQ() {
return ((org.spongycastle.jce.interfaces.ECPublicKey) key).getQ();
}
}

View File

@@ -4,27 +4,28 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.LocalAuthor;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Rating;
import net.sf.briar.api.messaging.RetentionAck;
import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.transport.Endpoint;
import net.sf.briar.api.transport.TemporarySecret;
@@ -82,16 +83,17 @@ interface Database<T> {
void commitTransaction(T txn) throws DbException;
/**
* Adds a contact with the given name to the database and returns an ID for
* the contact.
* Stores a contact with the given pseudonym, associated with the given
* local pseudonym, and returns an ID for the contact.
* <p>
* Locking: contact write, retention write, subscription write, transport
* write, window write.
*/
ContactId addContact(T txn, String name) throws DbException;
ContactId addContact(T txn, Author remote, AuthorId local)
throws DbException;
/**
* Adds an endpoint to the database.
* Stores an endpoint.
* <p>
* Locking: window write.
*/
@@ -108,7 +110,7 @@ interface Database<T> {
/**
* Stores a pseudonym that the user can use to sign messages.
* <p>
* Locking: identity write.
* Locking: contact write, identity write.
*/
void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
@@ -162,12 +164,13 @@ interface Database<T> {
boolean addSubscription(T txn, Group g) throws DbException;
/**
* Adds a new transport to the database and returns true if the transport
* was not previously in the database.
* Stores a transport and returns true if the transport was not previously
* in the database.
* <p>
* Locking: transport write, window write.
*/
boolean addTransport(T txn, TransportId t) throws DbException;
boolean addTransport(T txn, TransportId t, long maxLatency)
throws DbException;
/**
* Makes the given group visible to the given contact.
@@ -176,6 +179,13 @@ interface Database<T> {
*/
void addVisibility(T txn, ContactId c, GroupId g) throws DbException;
/**
* Returns true if the database contains the given contact.
* <p>
* Locking: contact read.
*/
boolean containsContact(T txn, AuthorId a) throws DbException;
/**
* Returns true if the database contains the given contact.
* <p>
@@ -277,6 +287,13 @@ interface Database<T> {
*/
long getLastConnected(T txn, ContactId c) throws DbException;
/**
* Returns the pseudonym with the given ID.
* <p>
* Locking: identitiy read.
*/
LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException;
/**
* Returns all pseudonyms that the user can use to sign messages.
* <p>
@@ -291,6 +308,14 @@ interface Database<T> {
*/
Collection<LocalGroup> getLocalGroups(T txn) throws DbException;
/**
* Returns the local transport properties for all transports.
* <p>
* Locking: transport read.
*/
Map<TransportId, TransportProperties> getLocalProperties(T txn)
throws DbException;
/**
* Returns the local transport properties for the given transport.
* <p>
@@ -509,6 +534,13 @@ interface Database<T> {
Collection<TransportAck> getTransportAcks(T txn, ContactId c)
throws DbException;
/**
* Returns the maximum latencies of all local transports.
* <p>
* Locking: transport read.
*/
Map<TransportId, Long> getTransportLatencies(T txn) throws DbException;
/**
* Returns a collection of transport updates for the given contact and
* updates their expiry times using the given latency. Returns null if no
@@ -669,6 +701,15 @@ interface Database<T> {
*/
boolean setReadFlag(T txn, MessageId m, boolean read) throws DbException;
/**
* Sets the remote transport properties for the given contact, replacing
* any existing properties.
* <p>
* Locking: transport write.
*/
void setRemoteProperties(T txn, ContactId c,
Map<TransportId, TransportProperties> p) throws DbException;
/**
* Updates the remote transport properties for the given contact and the
* given transport, replacing any existing properties, unless an update

View File

@@ -2,7 +2,7 @@ package net.sf.briar.db;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.GOOD;
import static net.sf.briar.api.messaging.Rating.GOOD;
import static net.sf.briar.db.DatabaseConstants.BYTES_PER_SWEEP;
import static net.sf.briar.db.DatabaseConstants.CRITICAL_FREE_SPACE;
import static net.sf.briar.db.DatabaseConstants.MAX_BYTES_BETWEEN_SPACE_CHECKS;
@@ -23,12 +23,16 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.db.ContactExistsException;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader;
@@ -57,22 +61,19 @@ import net.sf.briar.api.db.event.TransportAddedEvent;
import net.sf.briar.api.db.event.TransportRemovedEvent;
import net.sf.briar.api.lifecycle.ShutdownManager;
import net.sf.briar.api.messaging.Ack;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.LocalAuthor;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Offer;
import net.sf.briar.api.messaging.Rating;
import net.sf.briar.api.messaging.Request;
import net.sf.briar.api.messaging.RetentionAck;
import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.transport.Endpoint;
import net.sf.briar.api.transport.TemporarySecret;
@@ -183,7 +184,8 @@ DatabaseCleaner.Callback {
listeners.remove(d);
}
public ContactId addContact(String name) throws DbException {
public ContactId addContact(Author remote, AuthorId local)
throws DbException {
ContactId c;
contactLock.writeLock().lock();
try {
@@ -197,7 +199,9 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
c = db.addContact(txn, name);
if(db.containsContact(txn, remote.getId()))
throw new ContactExistsException();
c = db.addContact(txn, remote, local);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
@@ -257,6 +261,43 @@ DatabaseCleaner.Callback {
}
}
public void addLocalAuthor(LocalAuthor a) throws DbException {
contactLock.writeLock().lock();
try {
identityLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.addLocalAuthor(txn, a);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.writeLock().unlock();
}
} finally {
contactLock.writeLock().unlock();
}
}
public void addLocalGroup(LocalGroup g) throws DbException {
identityLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.addLocalGroup(txn, g);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.writeLock().unlock();
}
}
public void addLocalGroupMessage(Message m) throws DbException {
boolean added = false;
contactLock.readLock().lock();
@@ -430,38 +471,6 @@ DatabaseCleaner.Callback {
return true;
}
public void addLocalAuthor(LocalAuthor a) throws DbException {
identityLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.addLocalAuthor(txn, a);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.writeLock().unlock();
}
}
public void addLocalGroup(LocalGroup g) throws DbException {
identityLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
db.addLocalGroup(txn, g);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.writeLock().unlock();
}
}
public void addSecrets(Collection<TemporarySecret> secrets)
throws DbException {
contactLock.readLock().lock();
@@ -498,7 +507,8 @@ DatabaseCleaner.Callback {
}
}
public boolean addTransport(TransportId t) throws DbException {
public boolean addTransport(TransportId t, long maxLatency)
throws DbException {
boolean added;
transportLock.writeLock().lock();
try {
@@ -506,7 +516,7 @@ DatabaseCleaner.Callback {
try {
T txn = db.startTransaction();
try {
added = db.addTransport(txn, t);
added = db.addTransport(txn, t, maxLatency);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
@@ -518,7 +528,7 @@ DatabaseCleaner.Callback {
} finally {
transportLock.writeLock().unlock();
}
if(added) callListeners(new TransportAddedEvent(t));
if(added) callListeners(new TransportAddedEvent(t, maxLatency));
return added;
}
@@ -947,6 +957,23 @@ DatabaseCleaner.Callback {
}
}
public LocalAuthor getLocalAuthor(AuthorId a) throws DbException {
identityLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
LocalAuthor localAuthor = db.getLocalAuthor(txn, a);
db.commitTransaction(txn);
return localAuthor;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
identityLock.readLock().unlock();
}
}
public Collection<LocalAuthor> getLocalAuthors() throws DbException {
identityLock.readLock().lock();
try {
@@ -981,6 +1008,25 @@ DatabaseCleaner.Callback {
}
}
public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException {
transportLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
Map<TransportId, TransportProperties> properties =
db.getLocalProperties(txn);
db.commitTransaction(txn);
return properties;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.readLock().unlock();
}
}
public TransportProperties getLocalProperties(TransportId t)
throws DbException {
transportLock.readLock().lock();
@@ -1197,6 +1243,24 @@ DatabaseCleaner.Callback {
}
}
public Map<TransportId, Long> getTransportLatencies() throws DbException {
transportLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
Map<TransportId, Long> latencies =
db.getTransportLatencies(txn);
db.commitTransaction(txn);
return latencies;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.readLock().unlock();
}
}
public Map<GroupId, Integer> getUnreadMessageCounts() throws DbException {
messageLock.readLock().lock();
try {
@@ -1785,6 +1849,30 @@ DatabaseCleaner.Callback {
}
}
public void setRemoteProperties(ContactId c,
Map<TransportId, TransportProperties> p) throws DbException {
contactLock.readLock().lock();
try {
transportLock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsContact(txn, c))
throw new NoSuchContactException();
db.setRemoteProperties(txn, c, p);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.writeLock().unlock();
}
} finally {
contactLock.readLock().unlock();
}
}
public void setSeen(ContactId c, Collection<MessageId> seen)
throws DbException {
contactLock.readLock().lock();

View File

@@ -9,7 +9,6 @@ import java.util.Arrays;
import java.util.Properties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.Password;
import net.sf.briar.api.db.DatabaseConfig;
import net.sf.briar.api.db.DbException;
import net.sf.briar.util.FileUtils;
@@ -26,7 +25,7 @@ class H2Database extends JdbcDatabase {
private final File home;
private final String url;
private final Password password;
private final char[] password;
private final long maxSize;
@Inject
@@ -76,13 +75,12 @@ class H2Database extends JdbcDatabase {
@Override
protected Connection createConnection() throws SQLException {
Properties props = new Properties();
props.setProperty("user", "b");
char[] passwordArray = password.getPassword();
props.put("password", passwordArray);
props.setProperty("user", "user");
props.put("password", password);
try {
return DriverManager.getConnection(url, props);
} finally {
Arrays.fill(passwordArray, (char) 0);
Arrays.fill(password, (char) 0);
}
}
}

View File

@@ -4,9 +4,9 @@ import static java.sql.Types.BINARY;
import static java.sql.Types.VARCHAR;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.UNRATED;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
import static net.sf.briar.api.messaging.MessagingConstants.RETENTION_MODULUS;
import static net.sf.briar.api.messaging.Rating.UNRATED;
import static net.sf.briar.db.ExponentialBackoff.calculateExpiry;
import java.io.File;
@@ -27,30 +27,30 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.Contact;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.Rating;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.db.DbClosedException;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.LocalAuthor;
import net.sf.briar.api.messaging.LocalGroup;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Rating;
import net.sf.briar.api.messaging.RetentionAck;
import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.transport.Endpoint;
import net.sf.briar.api.transport.TemporarySecret;
@@ -85,8 +85,18 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_CONTACTS =
"CREATE TABLE contacts"
+ " (contactId COUNTER,"
+ " authorId HASH NOT NULL,"
+ " name VARCHAR NOT NULL,"
+ " PRIMARY KEY (contactId))";
+ " publicKey BINARY NOT NULL,"
+ " localAuthorId HASH NOT NULL,"
+ " PRIMARY KEY (contactId),"
+ " UNIQUE (authorId),"
+ " FOREIGN KEY (localAuthorId)"
+ " REFERENCES localAuthors (authorId)"
+ " ON DELETE RESTRICT)"; // Deletion not allowed
private static final String INDEX_CONTACTS_BY_AUTHOR =
"CREATE INDEX contactsByAuthor ON contacts (authorId)";
// Locking: subscription
// Dependents: message
@@ -234,7 +244,10 @@ abstract class JdbcDatabase implements Database<Connection> {
// Locking: transport
// Dependents: window
private static final String CREATE_TRANSPORTS =
"CREATE TABLE transports (transportId HASH NOT NULL)";
"CREATE TABLE transports"
+ " (transportId HASH NOT NULL,"
+ " maxLatency BIGINT NOT NULL,"
+ " PRIMARY KEY (transportId))";
// Locking: transport
private static final String CREATE_TRANSPORT_CONFIGS =
@@ -305,8 +318,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " (contactId INT NOT NULL,"
+ " transportId HASH NOT NULL,"
+ " epoch BIGINT NOT NULL,"
+ " clockDiff BIGINT NOT NULL,"
+ " latency BIGINT NOT NULL,"
+ " alice BOOLEAN NOT NULL,"
+ " PRIMARY KEY (contactId, transportId),"
+ " FOREIGN KEY (contactId)"
@@ -401,6 +412,7 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS));
s.executeUpdate(insertTypeNames(CREATE_LOCAL_GROUPS));
s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR);
s.executeUpdate(insertTypeNames(CREATE_GROUPS));
s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES));
s.executeUpdate(insertTypeNames(CREATE_CONTACT_GROUPS));
@@ -538,15 +550,20 @@ abstract class JdbcDatabase implements Database<Connection> {
if(interrupted) Thread.currentThread().interrupt();
}
public ContactId addContact(Connection txn, String name)
public ContactId addContact(Connection txn, Author remote, AuthorId local)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Create a contact row
String sql = "INSERT INTO contacts (name) VALUES (?)";
String sql = "INSERT INTO contacts"
+ " (authorId, name, publicKey, localAuthorId)"
+ " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setString(1, name);
ps.setBytes(1, remote.getId().getBytes());
ps.setString(2, remote.getName());
ps.setBytes(3, remote.getPublicKey());
ps.setBytes(4, local.getBytes());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -612,11 +629,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(2, t);
ps.addBatch();
}
int[] affectedBatch = ps.executeBatch();
if(affectedBatch.length != transports.size())
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != transports.size())
throw new DbStateException();
for(int i = 0; i < affectedBatch.length; i++) {
if(affectedBatch[i] != 1) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] != 1) throw new DbStateException();
}
ps.close();
return c;
@@ -630,16 +647,14 @@ abstract class JdbcDatabase implements Database<Connection> {
public void addEndpoint(Connection txn, Endpoint ep) throws DbException {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO endpoints (contactId, transportId,"
+ " epoch, clockDiff, latency, alice)"
+ " VALUES (?, ?, ?, ?, ?, ?)";
String sql = "INSERT INTO endpoints"
+ " (contactId, transportId, epoch, alice)"
+ " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, ep.getContactId().getInt());
ps.setBytes(2, ep.getTransportId().getBytes());
ps.setLong(3, ep.getEpoch());
ps.setLong(4, ep.getClockDifference());
ps.setLong(5, ep.getLatency());
ps.setBoolean(6, ep.getAlice());
ps.setBoolean(4, ep.getAlice());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -900,7 +915,7 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public boolean addTransport(Connection txn, TransportId t)
public boolean addTransport(Connection txn, TransportId t, long maxLatency)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
@@ -916,9 +931,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
if(found) return false;
// Create a transport row
sql = "INSERT INTO transports (transportId) VALUES (?)";
sql = "INSERT INTO transports (transportId, maxLatency)"
+ " VALUES (?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, t.getBytes());
ps.setLong(2, maxLatency);
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -984,6 +1001,27 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public boolean containsContact(Connection txn, AuthorId a)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM contacts WHERE authorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getBytes());
rs = ps.executeQuery();
boolean found = rs.next();
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
return found;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public boolean containsContact(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
@@ -1117,7 +1155,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT name, lastConnected"
String sql = "SELECT authorId, name, publicKey, lastConnected"
+ " FROM contacts AS c"
+ " JOIN connectionTimes AS ct"
+ " ON c.contactId = ct.contactId"
@@ -1126,11 +1164,14 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
if(!rs.next()) throw new DbStateException();
String name = rs.getString(1);
long lastConnected = rs.getLong(2);
AuthorId authorId = new AuthorId(rs.getBytes(1));
String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3);
long lastConnected = rs.getLong(4);
rs.close();
ps.close();
return new Contact(c, name, lastConnected);
Author author = new Author(authorId, name, publicKey);
return new Contact(c, author, lastConnected);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
@@ -1163,7 +1204,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT c.contactId, name, lastConnected"
String sql = "SELECT c.contactId, authorId, name, publicKey,"
+ " lastConnected"
+ " FROM contacts AS c"
+ " JOIN connectionTimes AS ct"
+ " ON c.contactId = ct.contactId";
@@ -1171,10 +1213,13 @@ abstract class JdbcDatabase implements Database<Connection> {
rs = ps.executeQuery();
List<Contact> contacts = new ArrayList<Contact>();
while(rs.next()) {
ContactId id = new ContactId(rs.getInt(1));
String name = rs.getString(2);
long lastConnected = rs.getLong(3);
contacts.add(new Contact(id, name, lastConnected));
ContactId contactId = new ContactId(rs.getInt(1));
AuthorId authorId = new AuthorId(rs.getBytes(2));
String name = rs.getString(3);
byte[] publicKey = rs.getBytes(4);
long lastConnected = rs.getLong(5);
Author author = new Author(authorId, name, publicKey);
contacts.add(new Contact(contactId, author, lastConnected));
}
rs.close();
ps.close();
@@ -1191,8 +1236,7 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT contactId, transportId, epoch, clockDiff,"
+ " latency, alice"
String sql = "SELECT contactId, transportId, epoch, alice"
+ " FROM endpoints";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
@@ -1201,11 +1245,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ContactId c = new ContactId(rs.getInt(1));
TransportId t = new TransportId(rs.getBytes(2));
long epoch = rs.getLong(3);
long clockDiff = rs.getLong(4);
long latency = rs.getLong(5);
boolean alice = rs.getBoolean(6);
endpoints.add(new Endpoint(c, t, epoch, clockDiff, latency,
alice));
boolean alice = rs.getBoolean(4);
endpoints.add(new Endpoint(c, t, epoch, alice));
}
return Collections.unmodifiableList(endpoints);
} catch(SQLException e) {
@@ -1287,6 +1328,30 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public LocalAuthor getLocalAuthor(Connection txn, AuthorId a)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT name, publicKey, privateKey FROM localAuthors"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
if(!rs.next()) throw new DbStateException();
AuthorId id = new AuthorId(rs.getBytes(1));
LocalAuthor localAuthor = new LocalAuthor(id, rs.getString(2),
rs.getBytes(3), rs.getBytes(4));
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
return localAuthor;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<LocalAuthor> getLocalAuthors(Connection txn)
throws DbException {
PreparedStatement ps = null;
@@ -1337,6 +1402,40 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Map<TransportId, TransportProperties> getLocalProperties(
Connection txn) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT transportId, key, value"
+ " FROM transportProperties"
+ " ORDER BY transportId";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
Map<TransportId, TransportProperties> properties =
new HashMap<TransportId, TransportProperties>();
TransportId lastId = null;
TransportProperties p = null;
while(rs.next()) {
TransportId id = new TransportId(rs.getBytes(1));
String key = rs.getString(2), value = rs.getString(3);
if(!id.equals(lastId)) {
p = new TransportProperties();
properties.put(id, p);
lastId = id;
}
p.put(key, value);
}
rs.close();
ps.close();
return Collections.unmodifiableMap(properties);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public TransportProperties getLocalProperties(Connection txn, TransportId t)
throws DbException {
PreparedStatement ps = null;
@@ -1437,86 +1536,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
Connection txn) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT m.messageId, parentId, contentType, subject,"
+ " timestamp, m.contactId, read, starred, seen"
+ " FROM messages AS m"
+ " JOIN statuses AS s"
+ " ON m.messageId = s.messageId"
+ " AND m.contactId = s.contactId"
+ " WHERE groupId IS NULL";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
List<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>();
while(rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
byte[] b = rs.getBytes(2);
MessageId parent = b == null ? null : new MessageId(b);
String contentType = rs.getString(3);
String subject = rs.getString(4);
long timestamp = rs.getLong(5);
ContactId contactId = new ContactId(rs.getInt(6));
boolean read = rs.getBoolean(7);
boolean starred = rs.getBoolean(8);
boolean seen = rs.getBoolean(9);
headers.add(new PrivateMessageHeader(id, parent, contentType,
subject, timestamp, read, starred, contactId, seen));
}
rs.close();
ps.close();
return Collections.unmodifiableList(headers);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
Connection txn, ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT m.messageId, parentId, contentType, subject,"
+ " timestamp, read, starred, seen"
+ " FROM messages AS m"
+ " JOIN statuses AS s"
+ " ON m.messageId = s.messageId"
+ " AND m.contactId = s.contactId"
+ " WHERE m.contactId = ? AND groupId IS NULL";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
List<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>();
while(rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
byte[] b = rs.getBytes(2);
MessageId parent = b == null ? null : new MessageId(b);
String contentType = rs.getString(3);
String subject = rs.getString(4);
long timestamp = rs.getLong(5);
boolean read = rs.getBoolean(6);
boolean starred = rs.getBoolean(7);
boolean seen = rs.getBoolean(8);
headers.add(new PrivateMessageHeader(id, parent, contentType,
subject, timestamp, read, starred, c, seen));
}
rs.close();
ps.close();
return Collections.unmodifiableList(headers);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<MessageId> getMessagesByAuthor(Connection txn, AuthorId a)
throws DbException {
PreparedStatement ps = null;
@@ -1680,6 +1699,86 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
Connection txn) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT m.messageId, parentId, contentType, subject,"
+ " timestamp, m.contactId, read, starred, seen"
+ " FROM messages AS m"
+ " JOIN statuses AS s"
+ " ON m.messageId = s.messageId"
+ " AND m.contactId = s.contactId"
+ " WHERE groupId IS NULL";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
List<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>();
while(rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
byte[] b = rs.getBytes(2);
MessageId parent = b == null ? null : new MessageId(b);
String contentType = rs.getString(3);
String subject = rs.getString(4);
long timestamp = rs.getLong(5);
ContactId contactId = new ContactId(rs.getInt(6));
boolean read = rs.getBoolean(7);
boolean starred = rs.getBoolean(8);
boolean seen = rs.getBoolean(9);
headers.add(new PrivateMessageHeader(id, parent, contentType,
subject, timestamp, read, starred, contactId, seen));
}
rs.close();
ps.close();
return Collections.unmodifiableList(headers);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
Connection txn, ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT m.messageId, parentId, contentType, subject,"
+ " timestamp, read, starred, seen"
+ " FROM messages AS m"
+ " JOIN statuses AS s"
+ " ON m.messageId = s.messageId"
+ " AND m.contactId = s.contactId"
+ " WHERE m.contactId = ? AND groupId IS NULL";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
List<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>();
while(rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
byte[] b = rs.getBytes(2);
MessageId parent = b == null ? null : new MessageId(b);
String contentType = rs.getString(3);
String subject = rs.getString(4);
long timestamp = rs.getLong(5);
boolean read = rs.getBoolean(6);
boolean starred = rs.getBoolean(7);
boolean seen = rs.getBoolean(8);
headers.add(new PrivateMessageHeader(id, parent, contentType,
subject, timestamp, read, starred, c, seen));
}
rs.close();
ps.close();
return Collections.unmodifiableList(headers);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Rating getRating(Connection txn, AuthorId a) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
@@ -1834,6 +1933,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if(!id.equals(lastId)) {
p = new TransportProperties();
properties.put(id, p);
lastId = id;
}
p.put(key, value);
}
@@ -1934,9 +2034,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT e.contactId, e.transportId, epoch,"
+ " clockDiff, latency, alice, period, secret, outgoing,"
+ " centre, bitmap"
String sql = "SELECT e.contactId, e.transportId, epoch, alice,"
+ " period, secret, outgoing, centre, bitmap"
+ " FROM endpoints AS e"
+ " JOIN secrets AS s"
+ " ON e.contactId = s.contactId"
@@ -1948,16 +2047,14 @@ abstract class JdbcDatabase implements Database<Connection> {
ContactId c = new ContactId(rs.getInt(1));
TransportId t = new TransportId(rs.getBytes(2));
long epoch = rs.getLong(3);
long clockDiff = rs.getLong(4);
long latency = rs.getLong(5);
boolean alice = rs.getBoolean(6);
long period = rs.getLong(7);
byte[] secret = rs.getBytes(8);
long outgoing = rs.getLong(9);
long centre = rs.getLong(10);
byte[] bitmap = rs.getBytes(11);
secrets.add(new TemporarySecret(c, t, epoch, clockDiff, latency,
alice, period, secret, outgoing, centre, bitmap));
boolean alice = rs.getBoolean(4);
long period = rs.getLong(5);
byte[] secret = rs.getBytes(6);
long outgoing = rs.getLong(7);
long centre = rs.getLong(8);
byte[] bitmap = rs.getBytes(9);
secrets.add(new TemporarySecret(c, t, epoch, alice, period,
secret, outgoing, centre, bitmap));
}
rs.close();
ps.close();
@@ -2265,11 +2362,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(2, a.getId().getBytes());
ps.addBatch();
}
int[] affectedBatch = ps.executeBatch();
if(affectedBatch.length != acks.size())
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != acks.size())
throw new DbStateException();
for(int i = 0; i < affectedBatch.length; i++) {
if(affectedBatch[i] < 1) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] < 1) throw new DbStateException();
}
ps.close();
return Collections.unmodifiableList(acks);
@@ -2280,6 +2377,29 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Map<TransportId, Long> getTransportLatencies(Connection txn)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT transportId, maxLatency FROM transports";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
Map<TransportId, Long> latencies = new HashMap<TransportId, Long>();
while(rs.next()){
TransportId id = new TransportId(rs.getBytes(1));
latencies.put(id, rs.getLong(2));
}
rs.close();
ps.close();
return Collections.unmodifiableMap(latencies);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<TransportUpdate> getTransportUpdates(Connection txn,
ContactId c, long maxLatency) throws DbException {
long now = clock.currentTimeMillis();
@@ -2642,11 +2762,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(2, c);
ps.addBatch();
}
int[] affectedBatch = ps.executeBatch();
if(affectedBatch.length != visible.size())
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != visible.size())
throw new DbStateException();
for(int i = 0; i < affectedBatch.length; i++) {
if(affectedBatch[i] != 1) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] != 1) throw new DbStateException();
}
ps.close();
} catch(SQLException e) {
@@ -2891,6 +3011,45 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void setRemoteProperties(Connection txn, ContactId c,
Map<TransportId, TransportProperties> p) throws DbException {
PreparedStatement ps = null;
try {
// Delete the existing properties, if any
String sql = "DELETE FROM contactTransportProperties"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.executeUpdate();
ps.close();
// Store the new properties
sql = "INSERT INTO contactTransportProperties"
+ " (contactId, transportId, key, value)"
+ " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
int batchSize = 0;
for(Entry<TransportId, TransportProperties> e : p.entrySet()) {
ps.setBytes(2, e.getKey().getBytes());
for(Entry<String, String> e1 : e.getValue().entrySet()) {
ps.setString(3, e1.getKey());
ps.setString(4, e1.getValue());
ps.addBatch();
batchSize++;
}
}
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != batchSize) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] != 1) throw new DbStateException();
}
ps.close();
} catch(SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
public void setRemoteProperties(Connection txn, ContactId c, TransportId t,
TransportProperties p, long version) throws DbException {
PreparedStatement ps = null;
@@ -3151,11 +3310,11 @@ abstract class JdbcDatabase implements Database<Connection> {
else ps.setNull(4, BINARY);
ps.addBatch();
}
int[] affectedBatch = ps.executeBatch();
if(affectedBatch.length != subs.size())
int[] batchAffected = ps.executeBatch();
if(batchAffected.length != subs.size())
throw new DbStateException();
for(int i = 0; i < affectedBatch.length; i++) {
if(affectedBatch[i] != 1) throw new DbStateException();
for(int i = 0; i < batchAffected.length; i++) {
if(batchAffected[i] != 1) throw new DbStateException();
}
ps.close();
} catch(SQLException e) {

View File

@@ -8,16 +8,30 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.Map;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.api.transport.ConnectionWriterFactory;
/** A connection thread for the peer being Alice in the invitation protocol. */
class AliceConnector extends Connector {
@@ -25,11 +39,17 @@ class AliceConnector extends Connector {
private static final Logger LOG =
Logger.getLogger(AliceConnector.class.getName());
AliceConnector(CryptoComponent crypto, ReaderFactory readerFactory,
WriterFactory writerFactory, Clock clock, ConnectorGroup group,
DuplexPlugin plugin, int localCode, int remoteCode) {
super(crypto, readerFactory, writerFactory, clock, group, plugin,
crypto.getPseudoRandom(localCode, remoteCode));
AliceConnector(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager, Clock clock,
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super(crypto, db, readerFactory, writerFactory, connectionReaderFactory,
connectionWriterFactory, authorFactory, keyManager, clock,
group, plugin, localAuthor, localProps, random);
}
@Override
@@ -65,7 +85,7 @@ class AliceConnector extends Connector {
byte[] hash = receivePublicKeyHash(r);
sendPublicKey(w);
byte[] key = receivePublicKey(r);
secret = deriveSharedSecret(hash, key, true);
secret = deriveMasterSecret(hash, key, true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
@@ -76,9 +96,10 @@ class AliceConnector extends Connector {
return;
}
// The key agreement succeeded - derive the confirmation codes
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " succeeded");
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
int[] codes = crypto.deriveConfirmationCodes(secret);
group.connectionSucceeded(codes[0], codes[1]);
int aliceCode = codes[0], bobCode = codes[1];
group.connectionSucceeded(aliceCode, bobCode);
// Exchange confirmation results
try {
sendConfirmation(w);
@@ -97,7 +118,59 @@ class AliceConnector extends Connector {
Thread.currentThread().interrupt();
return;
}
// That's all, folks!
// The timestamp is taken after exhanging confirmation results
long localTimestamp = clock.currentTimeMillis();
// Confirmation succeeded - upgrade to a secure connection
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " confirmation succeeded");
ConnectionReader connectionReader =
connectionReaderFactory.createInvitationConnectionReader(in,
secret, false);
r = readerFactory.createReader(connectionReader.getInputStream());
ConnectionWriter connectionWriter =
connectionWriterFactory.createInvitationConnectionWriter(out,
secret, true);
w = writerFactory.createWriter(connectionWriter.getOutputStream());
// Derive the invitation nonces
byte[][] nonces = crypto.deriveInvitationNonces(secret);
byte[] aliceNonce = nonces[0], bobNonce = nonces[1];
// Exchange pseudonyms, signed nonces, timestamps and transports
Author remoteAuthor;
long remoteTimestamp;
Map<TransportId, TransportProperties> remoteProps;
try {
sendPseudonym(w, aliceNonce);
sendTimestamp(w, localTimestamp);
sendTransportProperties(w);
remoteAuthor = receivePseudonym(r, bobNonce);
remoteTimestamp = receiveTimestamp(r);
remoteProps = receiveTransportProperties(r);
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
}
// The epoch is the minimum of the peers' timestamps
long epoch = Math.min(localTimestamp, remoteTimestamp);
// Add the contact and store the transports
try {
addContact(remoteAuthor, remoteProps, secret, epoch, true);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
}
// Pseudonym exchange succeeded
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " pseudonym exchange succeeded");
group.pseudonymExchangeSucceeded(remoteAuthor);
tryToClose(conn, false);
}
}

View File

@@ -8,16 +8,30 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.Map;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.api.transport.ConnectionWriterFactory;
/** A connection thread for the peer being Bob in the invitation protocol. */
class BobConnector extends Connector {
@@ -25,11 +39,17 @@ class BobConnector extends Connector {
private static final Logger LOG =
Logger.getLogger(BobConnector.class.getName());
BobConnector(CryptoComponent crypto, ReaderFactory readerFactory,
WriterFactory writerFactory, Clock clock, ConnectorGroup group,
DuplexPlugin plugin, int localCode, int remoteCode) {
super(crypto, readerFactory, writerFactory, clock, group, plugin,
crypto.getPseudoRandom(remoteCode, localCode));
BobConnector(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager, Clock clock,
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super(crypto, db, readerFactory, writerFactory, connectionReaderFactory,
connectionWriterFactory, authorFactory, keyManager, clock,
group, plugin, localAuthor, localProps, random);
}
@Override
@@ -65,7 +85,7 @@ class BobConnector extends Connector {
sendPublicKeyHash(w);
byte[] key = receivePublicKey(r);
sendPublicKey(w);
secret = deriveSharedSecret(hash, key, false);
secret = deriveMasterSecret(hash, key, false);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
@@ -76,14 +96,15 @@ class BobConnector extends Connector {
return;
}
// The key agreement succeeded - derive the confirmation codes
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " succeeded");
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
int[] codes = crypto.deriveConfirmationCodes(secret);
group.connectionSucceeded(codes[1], codes[0]);
int aliceCode = codes[0], bobCode = codes[1];
group.connectionSucceeded(bobCode, aliceCode);
// Exchange confirmation results
try {
sendConfirmation(w);
if(receiveConfirmation(r)) group.remoteConfirmationSucceeded();
else group.remoteConfirmationFailed();
sendConfirmation(w);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
@@ -97,7 +118,59 @@ class BobConnector extends Connector {
Thread.currentThread().interrupt();
return;
}
// That's all, folks!
// The timestamp is taken after exhanging confirmation results
long localTimestamp = clock.currentTimeMillis();
// Confirmation succeeded - upgrade to a secure connection
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " confirmation succeeded");
ConnectionReader connectionReader =
connectionReaderFactory.createInvitationConnectionReader(in,
secret, true);
r = readerFactory.createReader(connectionReader.getInputStream());
ConnectionWriter connectionWriter =
connectionWriterFactory.createInvitationConnectionWriter(out,
secret, false);
w = writerFactory.createWriter(connectionWriter.getOutputStream());
// Derive the nonces
byte[][] nonces = crypto.deriveInvitationNonces(secret);
byte[] aliceNonce = nonces[0], bobNonce = nonces[1];
// Exchange pseudonyms, signed nonces, timestamps and transports
Author remoteAuthor;
long remoteTimestamp;
Map<TransportId, TransportProperties> remoteProps;
try {
remoteAuthor = receivePseudonym(r, aliceNonce);
remoteTimestamp = receiveTimestamp(r);
remoteProps = receiveTransportProperties(r);
sendPseudonym(w, bobNonce);
sendTimestamp(w, localTimestamp);
sendTransportProperties(w);
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
}
// The epoch is the minimum of the peers' timestamps
long epoch = Math.min(localTimestamp, remoteTimestamp);
// Add the contact and store the transports
try {
addContact(remoteAuthor, remoteProps, secret, epoch, true);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
}
// Pseudonym exchange succeeded
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " pseudonym exchange succeeded");
group.pseudonymExchangeSucceeded(remoteAuthor);
tryToClose(conn, false);
}
}

View File

@@ -2,28 +2,53 @@ package net.sf.briar.invitation;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_AUTHOR_NAME_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PROPERTY_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PUBLIC_KEY_LENGTH;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_SIGNATURE_LENGTH;
import static net.sf.briar.api.plugins.InvitationConstants.CONNECTION_TIMEOUT;
import static net.sf.briar.api.plugins.InvitationConstants.HASH_LENGTH;
import static net.sf.briar.api.plugins.InvitationConstants.MAX_PUBLIC_KEY_LENGTH;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.Signature;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.UniqueId;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.NoSuchTransportException;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import net.sf.briar.api.transport.Endpoint;
abstract class Connector extends Thread {
@@ -31,11 +56,18 @@ abstract class Connector extends Thread {
Logger.getLogger(Connector.class.getName());
protected final CryptoComponent crypto;
protected final DatabaseComponent db;
protected final ReaderFactory readerFactory;
protected final WriterFactory writerFactory;
protected final ConnectionReaderFactory connectionReaderFactory;
protected final ConnectionWriterFactory connectionWriterFactory;
protected final AuthorFactory authorFactory;
protected final KeyManager keyManager;
protected final Clock clock;
protected final ConnectorGroup group;
protected final DuplexPlugin plugin;
protected final LocalAuthor localAuthor;
protected final Map<TransportId, TransportProperties> localProps;
protected final PseudoRandom random;
protected final String pluginName;
@@ -43,16 +75,28 @@ abstract class Connector extends Thread {
private final KeyParser keyParser;
private final MessageDigest messageDigest;
Connector(CryptoComponent crypto, ReaderFactory readerFactory,
WriterFactory writerFactory, Clock clock, ConnectorGroup group,
DuplexPlugin plugin, PseudoRandom random) {
Connector(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager, Clock clock,
ConnectorGroup group, DuplexPlugin plugin, LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super("Connector");
this.crypto = crypto;
this.db = db;
this.readerFactory = readerFactory;
this.writerFactory = writerFactory;
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.keyManager = keyManager;
this.clock = clock;
this.group = group;
this.plugin = plugin;
this.localAuthor = localAuthor;
this.localProps = localProps;
this.random = random;
pluginName = plugin.getClass().getName();
keyPair = crypto.generateAgreementKeyPair();
@@ -87,16 +131,6 @@ abstract class Connector extends Thread {
}
}
protected void tryToClose(DuplexTransportConnection conn,
boolean exception) {
try {
if(LOG.isLoggable(INFO)) LOG.info("Closing connection");
conn.dispose(exception, true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
protected void sendPublicKeyHash(Writer w) throws IOException {
w.writeBytes(messageDigest.digest(keyPair.getPublic().getEncoded()));
w.flush();
@@ -116,18 +150,15 @@ abstract class Connector extends Thread {
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent key");
}
protected byte[] receivePublicKey(Reader r) throws IOException {
protected byte[] receivePublicKey(Reader r) throws GeneralSecurityException,
IOException {
byte[] b = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
try {
keyParser.parsePublicKey(b);
} catch(GeneralSecurityException e) {
throw new FormatException();
}
keyParser.parsePublicKey(b);
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received key");
return b;
}
protected byte[] deriveSharedSecret(byte[] hash, byte[] key, boolean alice)
protected byte[] deriveMasterSecret(byte[] hash, byte[] key, boolean alice)
throws GeneralSecurityException {
// Check that the hash matches the key
if(!Arrays.equals(hash, messageDigest.digest(key))) {
@@ -135,16 +166,16 @@ abstract class Connector extends Thread {
LOG.info(pluginName + " hash does not match key");
throw new GeneralSecurityException();
}
// Derive the shared secret
return crypto.deriveInitialSecret(key, keyPair, alice);
// Derive the master secret
return crypto.deriveMasterSecret(key, keyPair, alice);
}
protected void sendConfirmation(Writer w) throws IOException,
InterruptedException {
boolean matched = group.waitForLocalConfirmationResult();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent confirmation");
w.writeBoolean(matched);
w.flush();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent confirmation");
}
protected boolean receiveConfirmation(Reader r) throws IOException {
@@ -153,4 +184,137 @@ abstract class Connector extends Thread {
LOG.info(pluginName + " received confirmation");
return matched;
}
protected void sendPseudonym(Writer w, byte[] nonce)
throws GeneralSecurityException, IOException {
// Sign the nonce
Signature signature = crypto.getSignature();
KeyParser keyParser = crypto.getSignatureKeyParser();
byte[] privateKey = localAuthor.getPrivateKey();
signature.initSign(keyParser.parsePrivateKey(privateKey));
signature.update(nonce);
byte[] sig = signature.sign();
// Write the name, public key and signature
w.writeString(localAuthor.getName());
w.writeBytes(localAuthor.getPublicKey());
w.writeBytes(sig);
w.flush();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent pseudonym");
}
protected Author receivePseudonym(Reader r, byte[] nonce)
throws GeneralSecurityException, IOException {
// Read the name, public key and signature
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] sig = r.readBytes(MAX_SIGNATURE_LENGTH);
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received pseudonym");
// Verify the signature
Signature signature = crypto.getSignature();
KeyParser keyParser = crypto.getSignatureKeyParser();
signature.initVerify(keyParser.parsePublicKey(publicKey));
signature.update(nonce);
if(!signature.verify(sig)) {
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " invalid signature");
throw new GeneralSecurityException();
}
return authorFactory.createAuthor(name, publicKey);
}
protected void sendTimestamp(Writer w, long timestamp) throws IOException {
w.writeInt64(timestamp);
w.flush();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent timestamp");
}
protected long receiveTimestamp(Reader r) throws IOException {
long timestamp = r.readInt64();
if(timestamp < 0) throw new FormatException();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received timestamp");
return timestamp;
}
protected void sendTransportProperties(Writer w) throws IOException {
w.writeListStart();
for(Entry<TransportId, TransportProperties> e : localProps.entrySet()) {
w.writeBytes(e.getKey().getBytes());
w.writeMap(e.getValue());
}
w.writeListEnd();
w.flush();
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " sent transport properties");
}
protected Map<TransportId, TransportProperties> receiveTransportProperties(
Reader r) throws IOException {
Map<TransportId, TransportProperties> remoteProps =
new HashMap<TransportId, TransportProperties>();
r.readListStart();
while(!r.hasListEnd()) {
byte[] b = r.readBytes(UniqueId.LENGTH);
if(b.length != UniqueId.LENGTH) throw new FormatException();
TransportId id = new TransportId(b);
r.setMaxStringLength(MAX_PROPERTY_LENGTH);
Map<String, String> p = r.readMap(String.class, String.class);
r.resetMaxStringLength();
remoteProps.put(id, new TransportProperties(p));
}
r.readListEnd();
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " received transport properties");
return remoteProps;
}
protected void addContact(Author remoteAuthor,
Map<TransportId, TransportProperties> remoteProps, byte[] secret,
long epoch, boolean alice) throws DbException {
// Add the contact to the database
ContactId c = db.addContact(remoteAuthor, localAuthor.getId());
// Store the remote transport properties
db.setRemoteProperties(c, remoteProps);
// Create an endpoint for each transport shared with the contact
List<TransportId> ids = new ArrayList<TransportId>();
for(TransportId id : localProps.keySet())
if(remoteProps.containsKey(id)) ids.add(id);
// Assign indices to the transports in a deterministic way
Collections.sort(ids, TransportIdComparator.INSTANCE);
int size = ids.size();
for(int i = 0; i < size; i++) {
Endpoint ep = new Endpoint(c, ids.get(i), epoch, alice);
try {
db.addEndpoint(ep);
} catch(NoSuchTransportException e) {
continue;
}
keyManager.endpointAdded(ep, crypto.deriveInitialSecret(secret, i));
}
}
protected void tryToClose(DuplexTransportConnection conn,
boolean exception) {
try {
if(LOG.isLoggable(INFO)) LOG.info("Closing connection");
conn.dispose(exception, true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private static class TransportIdComparator
implements Comparator<TransportId> {
private static final TransportIdComparator INSTANCE =
new TransportIdComparator();
public int compare(TransportId t1, TransportId t2) {
byte[] b1 = t1.getBytes(), b2 = t2.getBytes();
for(int i = 0; i < UniqueId.LENGTH; i++) {
if((b1[i] & 0xff) < (b2[i] & 0xff)) return -1;
if((b1[i] & 0xff) > (b2[i] & 0xff)) return 1;
}
return 0;
}
}
}

View File

@@ -6,13 +6,24 @@ import static net.sf.briar.api.plugins.InvitationConstants.CONFIRMATION_TIMEOUT;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.invitation.InvitationListener;
import net.sf.briar.api.invitation.InvitationState;
import net.sf.briar.api.invitation.InvitationTask;
@@ -20,6 +31,8 @@ import net.sf.briar.api.plugins.PluginManager;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriterFactory;
/** A task consisting of one or more parallel connection attempts. */
class ConnectorGroup extends Thread implements InvitationTask {
@@ -28,10 +41,16 @@ class ConnectorGroup extends Thread implements InvitationTask {
Logger.getLogger(ConnectorGroup.class.getName());
private final CryptoComponent crypto;
private final DatabaseComponent db;
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final ConnectionReaderFactory connectionReaderFactory;
private final ConnectionWriterFactory connectionWriterFactory;
private final AuthorFactory authorFactory;
private final KeyManager keyManager;
private final Clock clock;
private final PluginManager pluginManager;
private final AuthorId localAuthorId;
private final int localInvitationCode, remoteInvitationCode;
private final Collection<InvitationListener> listeners;
private final AtomicBoolean connected;
@@ -47,17 +66,27 @@ class ConnectorGroup extends Thread implements InvitationTask {
private boolean connectionFailed = false;
private boolean localCompared = false, remoteCompared = false;
private boolean localMatched = false, remoteMatched = false;
private String remoteName = null;
ConnectorGroup(CryptoComponent crypto, ReaderFactory readerFactory,
WriterFactory writerFactory, Clock clock,
PluginManager pluginManager, int localInvitationCode,
int remoteInvitationCode) {
ConnectorGroup(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager, Clock clock,
PluginManager pluginManager, AuthorId localAuthorId,
int localInvitationCode, int remoteInvitationCode) {
super("ConnectorGroup");
this.crypto = crypto;
this.db = db;
this.readerFactory = readerFactory;
this.writerFactory = writerFactory;
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.keyManager = keyManager;
this.clock = clock;
this.pluginManager = pluginManager;
this.localAuthorId = localAuthorId;
this.localInvitationCode = localInvitationCode;
this.remoteInvitationCode = remoteInvitationCode;
listeners = new CopyOnWriteArrayList<InvitationListener>();
@@ -68,8 +97,9 @@ class ConnectorGroup extends Thread implements InvitationTask {
public synchronized InvitationState addListener(InvitationListener l) {
listeners.add(l);
return new InvitationState(localInvitationCode, remoteInvitationCode,
localConfirmationCode, remoteConfirmationCode, connectionFailed,
localCompared, remoteCompared, localMatched, remoteMatched);
localConfirmationCode, remoteConfirmationCode,
connectionFailed, localCompared, remoteCompared, localMatched,
remoteMatched, remoteName);
}
public void removeListener(InvitationListener l) {
@@ -82,22 +112,34 @@ class ConnectorGroup extends Thread implements InvitationTask {
@Override
public void run() {
LocalAuthor localAuthor;
Map<TransportId, TransportProperties> localProps;
// Load the local pseudonym and transport properties
try {
localAuthor = db.getLocalAuthor(localAuthorId);
localProps = db.getLocalProperties();
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
synchronized(this) {
connectionFailed = true;
}
for(InvitationListener l : listeners) l.connectionFailed();
return;
}
// Start the connection threads
Collection<Connector> connectors = new ArrayList<Connector>();
// Alice is the party with the smaller invitation code
if(localInvitationCode < remoteInvitationCode) {
for(DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
Connector c = new AliceConnector(crypto, readerFactory,
writerFactory, clock, this, plugin, localInvitationCode,
remoteInvitationCode);
Connector c = createAliceConnector(plugin, localAuthor,
localProps);
connectors.add(c);
c.start();
}
} else {
for(DuplexPlugin plugin: pluginManager.getInvitationPlugins()) {
Connector c = (new BobConnector(crypto, readerFactory,
writerFactory, clock, this, plugin, localInvitationCode,
remoteInvitationCode));
Connector c = createBobConnector(plugin, localAuthor,
localProps);
connectors.add(c);
c.start();
}
@@ -118,6 +160,28 @@ class ConnectorGroup extends Thread implements InvitationTask {
}
}
private Connector createAliceConnector(DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps) {
PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
remoteInvitationCode);
return new AliceConnector(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory, authorFactory,
keyManager, clock, this, plugin, localAuthor, localProps,
random);
}
private Connector createBobConnector(DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps) {
PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
localInvitationCode);
return new BobConnector(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory, authorFactory,
keyManager, clock, this, plugin, localAuthor, localProps,
random);
}
public void localConfirmationSucceeded() {
synchronized(this) {
localCompared = true;
@@ -167,4 +231,17 @@ class ConnectorGroup extends Thread implements InvitationTask {
return localMatched;
}
}
void pseudonymExchangeSucceeded(Author remoteAuthor) {
String name = remoteAuthor.getName();
synchronized(this) {
remoteName = name;
}
for(InvitationListener l : listeners)
l.pseudonymExchangeSucceeded(name);
}
void pseudonymExchangeFailed() {
for(InvitationListener l : listeners) l.pseudonymExchangeFailed();
}
}

View File

@@ -1,36 +1,58 @@
package net.sf.briar.invitation;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.invitation.InvitationTask;
import net.sf.briar.api.invitation.InvitationTaskFactory;
import net.sf.briar.api.plugins.PluginManager;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import com.google.inject.Inject;
class InvitationTaskFactoryImpl implements InvitationTaskFactory {
private final CryptoComponent crypto;
private final DatabaseComponent db;
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final ConnectionReaderFactory connectionReaderFactory;
private final ConnectionWriterFactory connectionWriterFactory;
private final AuthorFactory authorFactory;
private final KeyManager keyManager;
private final Clock clock;
private final PluginManager pluginManager;
@Inject
InvitationTaskFactoryImpl(CryptoComponent crypto,
InvitationTaskFactoryImpl(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
Clock clock, PluginManager pluginManager) {
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, KeyManager keyManager, Clock clock,
PluginManager pluginManager) {
this.crypto = crypto;
this.db = db;
this.readerFactory = readerFactory;
this.writerFactory = writerFactory;
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.keyManager = keyManager;
this.clock = clock;
this.pluginManager = pluginManager;
}
public InvitationTask createTask(int localCode, int remoteCode) {
return new ConnectorGroup(crypto, readerFactory, writerFactory, clock,
pluginManager, localCode, remoteCode);
public InvitationTask createTask(AuthorId localAuthorId, int localCode,
int remoteCode) {
return new ConnectorGroup(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory,
authorFactory, keyManager, clock, pluginManager, localAuthorId,
localCode, remoteCode);
}
}

View File

@@ -5,11 +5,11 @@ import static net.sf.briar.api.messaging.Types.AUTHOR;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorFactory;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;

View File

@@ -6,10 +6,10 @@ import static net.sf.briar.api.messaging.Types.AUTHOR;
import java.io.IOException;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.serial.DigestingConsumer;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.StructReader;

View File

@@ -20,10 +20,10 @@ import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Signature;
import net.sf.briar.api.Author;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageFactory;

View File

@@ -1,7 +1,7 @@
package net.sf.briar.messaging;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.Author;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;

View File

@@ -13,11 +13,11 @@ import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import net.sf.briar.api.Author;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.UniqueId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.UniqueId;
import net.sf.briar.api.messaging.UnverifiedMessage;
import net.sf.briar.api.serial.CopyingConsumer;
import net.sf.briar.api.serial.CountingConsumer;

View File

@@ -4,10 +4,10 @@ import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.security.Signature;
import net.sf.briar.api.Author;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;

View File

@@ -1,8 +1,8 @@
package net.sf.briar.messaging;
import net.sf.briar.api.Author;
import net.sf.briar.api.AuthorFactory;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorFactory;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupFactory;
import net.sf.briar.api.messaging.MessageFactory;

View File

@@ -24,7 +24,9 @@ import java.util.Map;
import net.sf.briar.api.Bytes;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.UniqueId;
import net.sf.briar.api.messaging.Ack;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.Offer;
@@ -35,9 +37,7 @@ import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.messaging.UniqueId;
import net.sf.briar.api.messaging.UnverifiedMessage;
import net.sf.briar.api.serial.Consumer;
import net.sf.briar.api.serial.CountingConsumer;

View File

@@ -2,8 +2,8 @@ package net.sf.briar.messaging.duplex;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.GOOD;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import static net.sf.briar.api.messaging.Rating.GOOD;
import java.io.IOException;
import java.io.InputStream;
@@ -23,6 +23,7 @@ import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.crypto.CryptoExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
@@ -55,7 +56,6 @@ import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.messaging.UnverifiedMessage;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;

View File

@@ -6,6 +6,7 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.crypto.CryptoExecutor;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.db.DatabaseComponent;
@@ -13,7 +14,6 @@ import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.messaging.MessageVerifier;
import net.sf.briar.api.messaging.PacketReaderFactory;
import net.sf.briar.api.messaging.PacketWriterFactory;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.duplex.DuplexConnectionFactory;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.api.transport.ConnectionContext;

View File

@@ -10,6 +10,7 @@ import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.crypto.CryptoExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
@@ -24,7 +25,6 @@ import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.messaging.UnverifiedMessage;
import net.sf.briar.api.plugins.simplex.SimplexTransportReader;

View File

@@ -10,6 +10,7 @@ import java.util.Collection;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.messaging.Ack;
@@ -20,7 +21,6 @@ import net.sf.briar.api.messaging.RetentionUpdate;
import net.sf.briar.api.messaging.SubscriptionAck;
import net.sf.briar.api.messaging.SubscriptionUpdate;
import net.sf.briar.api.messaging.TransportAck;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.TransportUpdate;
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
import net.sf.briar.api.transport.ConnectionContext;

View File

@@ -6,6 +6,7 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.crypto.CryptoExecutor;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.db.DatabaseComponent;
@@ -13,7 +14,6 @@ import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.messaging.MessageVerifier;
import net.sf.briar.api.messaging.PacketReaderFactory;
import net.sf.briar.api.messaging.PacketWriterFactory;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.messaging.simplex.SimplexConnectionFactory;
import net.sf.briar.api.plugins.simplex.SimplexTransportReader;
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;

View File

@@ -16,11 +16,11 @@ import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.Plugin;
import net.sf.briar.api.plugins.PluginCallback;
import net.sf.briar.api.plugins.PluginExecutor;
@@ -88,12 +88,6 @@ class PluginManagerImpl implements PluginManager {
LOG.warning("Duplicate transport ID: " + id);
continue;
}
try {
db.addTransport(id);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
continue;
}
SimplexCallback callback = new SimplexCallback(id);
SimplexPlugin plugin = factory.createPlugin(callback);
if(plugin == null) {
@@ -103,6 +97,12 @@ class PluginManagerImpl implements PluginManager {
}
continue;
}
try {
db.addTransport(id, plugin.getMaxLatency());
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
continue;
}
try {
if(plugin.start()) {
simplexPlugins.add(plugin);
@@ -124,12 +124,6 @@ class PluginManagerImpl implements PluginManager {
LOG.warning("Duplicate transport ID: " + id);
continue;
}
try {
db.addTransport(id);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
continue;
}
DuplexCallback callback = new DuplexCallback(id);
DuplexPlugin plugin = factory.createPlugin(callback);
if(plugin == null) {
@@ -139,6 +133,12 @@ class PluginManagerImpl implements PluginManager {
}
continue;
}
try {
db.addTransport(id, plugin.getMaxLatency());
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
continue;
}
try {
if(plugin.start()) {
duplexPlugins.add(plugin);
@@ -197,10 +197,10 @@ class PluginManagerImpl implements PluginManager {
}
public synchronized Collection<DuplexPlugin> getInvitationPlugins() {
Collection<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
for(DuplexPlugin d : duplexPlugins)
if(d.supportsInvitations()) supported.add(d);
return supported;
return Collections.unmodifiableList(supported);
}
private abstract class PluginCallbackImpl implements PluginCallback {

View File

@@ -26,10 +26,10 @@ import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -3,9 +3,9 @@ package net.sf.briar.plugins.bluetooth;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -24,10 +24,10 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -3,8 +3,8 @@ package net.sf.briar.plugins.droidtooth;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -12,7 +12,7 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
import net.sf.briar.util.StringUtils;

View File

@@ -2,7 +2,7 @@ package net.sf.briar.plugins.file;
import java.util.concurrent.Executor;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.simplex.SimplexPlugin;
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;

View File

@@ -17,9 +17,9 @@ import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -2,7 +2,7 @@ package net.sf.briar.plugins.modem;
import java.util.concurrent.Executor;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;

View File

@@ -21,10 +21,10 @@ import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.messaging.TransportId;
import net.sf.briar.api.plugins.PluginExecutor;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;

Some files were not shown because too many files have changed in this diff Show More