Commit 0587fdc5 authored by akwizgran's avatar akwizgran

Add handshake key pairs to DB, remove inactive contacts.

parent dcebd5a8
Pipeline #3208 passed with stage
in 10 minutes and 1 second
......@@ -8,6 +8,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.util.StringUtils.toUtf8;
@Immutable
......@@ -19,21 +20,28 @@ public class Contact {
private final AuthorId localAuthorId;
@Nullable
private final String alias;
private final boolean verified, active;
@Nullable
private final byte[] handshakePublicKey;
private final boolean verified;
public Contact(ContactId id, Author author, AuthorId localAuthorId,
@Nullable String alias, boolean verified, boolean active) {
@Nullable String alias, @Nullable byte[] handshakePublicKey,
boolean verified) {
if (alias != null) {
int aliasLength = toUtf8(alias).length;
if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
throw new IllegalArgumentException();
}
if (handshakePublicKey != null && (handshakePublicKey.length == 0 ||
handshakePublicKey.length > MAX_PUBLIC_KEY_LENGTH)) {
throw new IllegalArgumentException();
}
this.id = id;
this.author = author;
this.localAuthorId = localAuthorId;
this.alias = alias;
this.handshakePublicKey = handshakePublicKey;
this.verified = verified;
this.active = active;
}
public ContactId getId() {
......@@ -53,12 +61,13 @@ public class Contact {
return alias;
}
public boolean isVerified() {
return verified;
@Nullable
public byte[] getHandshakePublicKey() {
return handshakePublicKey;
}
public boolean isActive() {
return active;
public boolean isVerified() {
return verified;
}
@Override
......
......@@ -39,7 +39,7 @@ public interface ContactManager {
* and returns an ID for the contact.
*/
ContactId addContact(Transaction txn, Author remote, AuthorId local,
boolean verified, boolean active) throws DbException;
boolean verified) throws DbException;
/**
* Stores a contact associated with the given local and remote pseudonyms,
......@@ -108,7 +108,7 @@ public interface ContactManager {
/**
* Returns all active contacts.
*/
Collection<Contact> getActiveContacts() throws DbException;
Collection<Contact> getContacts() throws DbException;
/**
* Removes a contact and all associated state.
......@@ -120,12 +120,6 @@ public interface ContactManager {
*/
void removeContact(Transaction txn, ContactId c) throws DbException;
/**
* Marks a contact as active or inactive.
*/
void setContactActive(Transaction txn, ContactId c, boolean active)
throws DbException;
/**
* Sets an alias name for the contact or unsets it if alias is null.
*/
......
......@@ -14,18 +14,12 @@ import javax.annotation.concurrent.Immutable;
public class ContactAddedEvent extends Event {
private final ContactId contactId;
private final boolean active;
public ContactAddedEvent(ContactId contactId, boolean active) {
public ContactAddedEvent(ContactId contactId) {
this.contactId = contactId;
this.active = active;
}
public ContactId getContactId() {
return contactId;
}
public boolean isActive() {
return active;
}
}
package org.briarproject.bramble.api.contact.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a contact is marked active or inactive.
*/
@Immutable
@NotNullByDefault
public class ContactStatusChangedEvent extends Event {
private final ContactId contactId;
private final boolean active;
public ContactStatusChangedEvent(ContactId contactId, boolean active) {
this.contactId = contactId;
this.active = active;
}
public ContactId getContactId() {
return contactId;
}
public boolean isActive() {
return active;
}
}
......@@ -106,7 +106,7 @@ public interface DatabaseComponent {
* and returns an ID for the contact.
*/
ContactId addContact(Transaction txn, Author remote, AuthorId local,
boolean verified, boolean active) throws DbException;
boolean verified) throws DbException;
/**
* Stores a group.
......@@ -590,12 +590,6 @@ public interface DatabaseComponent {
*/
void setContactVerified(Transaction txn, ContactId c) throws DbException;
/**
* Marks the given contact as active or inactive.
*/
void setContactActive(Transaction txn, ContactId c, boolean active)
throws DbException;
/**
* Sets an alias name for the contact or unsets it if alias is null.
*/
......
......@@ -2,8 +2,11 @@ package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
/**
* A pseudonym for the local user.
*/
......@@ -12,6 +15,8 @@ import javax.annotation.concurrent.Immutable;
public class LocalAuthor extends Author {
private final byte[] privateKey;
@Nullable
private final byte[] handshakePublicKey, handshakePrivateKey;
private final long created;
public LocalAuthor(AuthorId id, int formatVersion, String name,
......@@ -19,6 +24,22 @@ public class LocalAuthor extends Author {
super(id, formatVersion, name, publicKey);
this.privateKey = privateKey;
this.created = created;
handshakePublicKey = null;
handshakePrivateKey = null;
}
public LocalAuthor(AuthorId id, int formatVersion, String name,
byte[] publicKey, byte[] privateKey, byte[] handshakePublicKey,
byte[] handshakePrivateKey, long created) {
super(id, formatVersion, name, publicKey);
if (handshakePublicKey.length == 0 ||
handshakePublicKey.length > MAX_PUBLIC_KEY_LENGTH) {
throw new IllegalArgumentException();
}
this.privateKey = privateKey;
this.handshakePublicKey = handshakePublicKey;
this.handshakePrivateKey = handshakePrivateKey;
this.created = created;
}
/**
......@@ -28,6 +49,22 @@ public class LocalAuthor extends Author {
return privateKey;
}
/**
* Returns the public key used for handshaking, or null if no key exists.
*/
@Nullable
public byte[] getHandshakePublicKey() {
return handshakePublicKey;
}
/**
* Returns the private key used for handshaking, or null if no key exists.
*/
@Nullable
public byte[] getHandshakePrivateKey() {
return handshakePrivateKey;
}
/**
* Returns the time the pseudonym was created, in milliseconds since the
* Unix epoch.
......
......@@ -23,9 +23,8 @@ public interface KeyManager {
* <p/>
* {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned.
* @param alice true if the local party is Alice
*
* @param alice true if the local party is Alice
* @param active whether the derived keys can be used for outgoing streams
*/
Map<TransportId, TransportKeySetId> addContact(Transaction txn, ContactId c,
SecretKey rootKey, long timestamp, boolean alice, boolean active)
......
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey;
......@@ -44,6 +46,7 @@ public class TestUtils {
new AtomicInteger((int) (Math.random() * 1000 * 1000));
private static final Random random = new Random();
private static final long timestamp = System.currentTimeMillis();
private static final AtomicInteger nextContactId = new AtomicInteger(1);
public static File getTestDirectory() {
int name = nextTestDir.getAndIncrement();
......@@ -155,6 +158,27 @@ public class TestUtils {
timestamp);
}
public static ContactId getContactId() {
return new ContactId(nextContactId.getAndIncrement());
}
public static Contact getContact() {
return getContact(getAuthor(), new AuthorId(getRandomId()),
random.nextBoolean());
}
public static Contact getContact(Author remote, AuthorId local,
boolean verified) {
return getContact(getContactId(), remote, local, verified);
}
public static Contact getContact(ContactId c, Author remote, AuthorId local,
boolean verified) {
return new Contact(c, remote, local,
getRandomString(MAX_AUTHOR_NAME_LENGTH),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), verified);
}
public static double getMedian(Collection<? extends Number> samples) {
int size = samples.size();
if (size == 0) throw new IllegalArgumentException();
......
......@@ -18,7 +18,6 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.KeyManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
......@@ -72,7 +71,7 @@ class ContactManagerImpl implements ContactManager {
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
SecretKey rootKey, long timestamp, boolean alice, boolean verified,
boolean active) throws DbException {
ContactId c = db.addContact(txn, remote, local, verified, active);
ContactId c = db.addContact(txn, remote, local, verified);
keyManager.addContact(txn, c, rootKey, timestamp, alice, active);
Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
......@@ -81,8 +80,8 @@ class ContactManagerImpl implements ContactManager {
@Override
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
boolean verified, boolean active) throws DbException {
ContactId c = db.addContact(txn, remote, local, verified, active);
boolean verified) throws DbException {
ContactId c = db.addContact(txn, remote, local, verified);
Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
return c;
......@@ -165,12 +164,8 @@ class ContactManagerImpl implements ContactManager {
}
@Override
public Collection<Contact> getActiveContacts() throws DbException {
Collection<Contact> contacts =
db.transactionWithResult(true, db::getContacts);
List<Contact> active = new ArrayList<>(contacts.size());
for (Contact c : contacts) if (c.isActive()) active.add(c);
return active;
public Collection<Contact> getContacts() throws DbException {
return db.transactionWithResult(true, db::getContacts);
}
@Override
......@@ -178,12 +173,6 @@ class ContactManagerImpl implements ContactManager {
db.transaction(false, txn -> removeContact(txn, c));
}
@Override
public void setContactActive(Transaction txn, ContactId c, boolean active)
throws DbException {
db.setContactActive(txn, c, active);
}
@Override
public void setContactAlias(Transaction txn, ContactId c,
@Nullable String alias) throws DbException {
......
......@@ -90,8 +90,8 @@ interface Database<T> {
* Stores a contact associated with the given local and remote pseudonyms,
* and returns an ID for the contact.
*/
ContactId addContact(T txn, Author remote, AuthorId local, boolean verified,
boolean active) throws DbException;
ContactId addContact(T txn, Author remote, AuthorId local, boolean verified)
throws DbException;
/**
* Stores a group.
......@@ -672,12 +672,6 @@ interface Database<T> {
*/
void setContactVerified(T txn, ContactId c) throws DbException;
/**
* Marks the given contact as active or inactive.
*/
void setContactActive(T txn, ContactId c, boolean active)
throws DbException;
/**
* Sets an alias name for a contact.
*/
......
......@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.CommitAction;
......@@ -234,7 +233,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override
public ContactId addContact(Transaction transaction, Author remote,
AuthorId local, boolean verified, boolean active)
AuthorId local, boolean verified)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
......@@ -244,9 +243,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new ContactExistsException();
if (db.containsContact(txn, remote.getId(), local))
throw new ContactExistsException();
ContactId c = db.addContact(txn, remote, local, verified, active);
transaction.attach(new ContactAddedEvent(c, active));
if (active) transaction.attach(new ContactStatusChangedEvent(c, true));
ContactId c = db.addContact(txn, remote, local, verified);
transaction.attach(new ContactAddedEvent(c));
return c;
}
......@@ -969,17 +967,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new ContactVerifiedEvent(c));
}
@Override
public void setContactActive(Transaction transaction, ContactId c,
boolean active) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
db.setContactActive(txn, c, active);
transaction.attach(new ContactStatusChangedEvent(c, active));
}
@Override
public void setContactAlias(Transaction transaction, ContactId c,
@Nullable String alias) throws DbException {
......
......@@ -44,7 +44,6 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
......@@ -64,6 +63,7 @@ import javax.annotation.Nullable;
import static java.sql.Types.BINARY;
import static java.sql.Types.INTEGER;
import static java.sql.Types.VARCHAR;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
......@@ -113,6 +113,8 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " name _STRING NOT NULL,"
+ " publicKey _BINARY NOT NULL,"
+ " privateKey _BINARY NOT NULL,"
+ " handshakePublicKey _BINARY," // Null if not generated
+ " handshakePrivateKey _BINARY," // Null if not generated
+ " created BIGINT NOT NULL,"
+ " PRIMARY KEY (authorId))";
......@@ -122,11 +124,11 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " authorId _HASH NOT NULL,"
+ " formatVersion INT NOT NULL,"
+ " name _STRING NOT NULL,"
+ " alias _STRING," // Null if no alias exists
+ " alias _STRING," // Null if no alias has been set
+ " publicKey _BINARY NOT NULL,"
+ " handshakePublicKey _BINARY," // Null if key is unknown
+ " localAuthorId _HASH NOT NULL,"
+ " verified BOOLEAN NOT NULL,"
+ " active BOOLEAN NOT NULL,"
+ " PRIMARY KEY (contactId),"
+ " FOREIGN KEY (localAuthorId)"
+ " REFERENCES localAuthors (authorId)"
......@@ -479,11 +481,12 @@ abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing
List<Migration<Connection>> getMigrations() {
return Arrays.asList(
return asList(
new Migration38_39(),
new Migration39_40(),
new Migration40_41(dbTypes),
new Migration41_42(dbTypes)
new Migration41_42(dbTypes),
new Migration42_43(dbTypes)
);
}
......@@ -660,16 +663,15 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override
public ContactId addContact(Connection txn, Author remote, AuthorId local,
boolean verified, boolean active) throws DbException {
boolean verified) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Create a contact row
String sql = "INSERT INTO contacts"
+ " (authorId, formatVersion, name, publicKey,"
+ " localAuthorId,"
+ " verified, active)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
+ " localAuthorId, verified)"
+ " VALUES (?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, remote.getId().getBytes());
ps.setInt(2, remote.getFormatVersion());
......@@ -677,7 +679,6 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(4, remote.getPublicKey());
ps.setBytes(5, local.getBytes());
ps.setBoolean(6, verified);
ps.setBoolean(7, active);
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
......@@ -878,16 +879,20 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO localAuthors"
+ " (authorId, formatVersion, name, publicKey,"
+ " privateKey, created)"
+ " VALUES (?, ?, ?, ?, ?, ?)";
+ " (authorId, formatVersion, name, publicKey, privateKey,"
+ " handshakePublicKey, handshakePrivateKey, created)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getId().getBytes());
ps.setInt(2, a.getFormatVersion());
ps.setString(3, a.getName());
ps.setBytes(4, a.getPublicKey());
ps.setBytes(5, a.getPrivateKey());
ps.setLong(6, a.getTimeCreated());
if (a.getHandshakePublicKey() == null) ps.setNull(6, BINARY);
else ps.setBytes(6, a.getHandshakePublicKey());
if (a.getHandshakePrivateKey() == null) ps.setNull(7, BINARY);
else ps.setBytes(7, a.getHandshakePrivateKey());
ps.setLong(8, a.getTimeCreated());
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
......@@ -1427,7 +1432,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ResultSet rs = null;
try {
String sql = "SELECT authorId, formatVersion, name, alias,"
+ " publicKey, localAuthorId, verified, active"
+ " publicKey, handshakePublicKey, localAuthorId, verified"
+ " FROM contacts"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
......@@ -1439,15 +1444,15 @@ abstract class JdbcDatabase implements Database<Connection> {
String name = rs.getString(3);
String alias = rs.getString(4);
byte[] publicKey = rs.getBytes(5);
AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
boolean verified = rs.getBoolean(7);
boolean active = rs.getBoolean(8);
byte[] handshakePublicKey = rs.getBytes(6);
AuthorId localAuthorId = new AuthorId(rs.getBytes(7));
boolean verified = rs.getBoolean(8);
rs.close();
ps.close();
Author author =
new Author(authorId, formatVersion, name, publicKey);
return new Contact(c, author, localAuthorId, alias, verified,
active);
return new Contact(c, author, localAuthorId, alias,
handshakePublicKey, verified);
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
......@@ -1456,13 +1461,13 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public Collection<Contact> getContacts(Connection txn)
throws DbException {
public Collection<Contact> getContacts(Connection txn) throws DbException {
Statement s = null;
ResultSet rs = null;
try {
String sql = "SELECT contactId, authorId, formatVersion, name,"
+ " alias, publicKey, localAuthorId, verified, active"
+ " alias, publicKey, handshakePublicKey, localAuthorId,"
+ " verified"
+ " FROM contacts";
s = txn.createStatement();
rs = s.executeQuery(sql);
......@@ -1474,13 +1479,13 @@ abstract class JdbcDatabase implements Database<Connection> {
String name = rs.getString(4);
String alias = rs.getString(5);
byte[] publicKey = rs.getBytes(6);
byte[] handshakePublicKey = rs.getBytes(7);
AuthorId localAuthorId = new AuthorId(rs.getBytes(8));
boolean verified = rs.getBoolean(9);
Author author =
new Author(authorId, formatVersion, name, publicKey);
AuthorId localAuthorId = new AuthorId(rs.getBytes(7));
boolean verified = rs.getBoolean(8);
boolean active = rs.getBoolean(9);
contacts.add(new Contact(contactId, author, localAuthorId,
alias, verified, active));
alias, handshakePublicKey, verified));
}
rs.close();
s.close();
......@@ -1522,7 +1527,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ResultSet rs = null;
try {
String sql = "SELECT contactId, formatVersion, name, alias,"
+ " publicKey, localAuthorId, verified, active"
+ " publicKey, handshakePublicKey, localAuthorId, verified"
+ " FROM contacts"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
......@@ -1530,18 +1535,18 @@ abstract class JdbcDatabase implements Database<Connection> {
rs = ps.executeQuery();
List<Contact> contacts = new ArrayList<>();
while (rs.next()) {
ContactId c = new ContactId(rs.getInt(1));
ContactId contactId = new ContactId(rs.getInt(1));
int formatVersion = rs.getInt(2);
String name = rs.getString(3);
String alias = rs.getString(4);
byte[] publicKey = rs.getBytes(5);
AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
boolean verified = rs.getBoolean(7);
boolean active = rs.getBoolean(8);
byte[] handshakePublicKey = rs.getBytes(6);
AuthorId localAuthorId = new AuthorId(rs.getBytes(7));
boolean verified = rs.getBoolean(8);
Author author =
new Author(remote, formatVersion, name, publicKey);
contacts.add(new Contact(c, author, localAuthorId, alias,
verified, active));
contacts.add(new Contact(contactId, author, localAuthorId,
alias, handshakePublicKey, verified));
}
rs.close();
ps.close();
......@@ -1661,8 +1666,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT formatVersion, name, publicKey,"
+ " privateKey, created"
String sql = "SELECT formatVersion, name, publicKey, privateKey,"
+ " handshakePublicKey, handshakePrivateKey, created"
+ " FROM localAuthors"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
......@@ -1673,9 +1678,12 @@ abstract class JdbcDatabase implements Database<Connection> {
String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3);
byte[] privateKey = rs.getBytes(4);
byte[] handshakePublicKey = rs.getBytes(5);
byte[] handshakePrivateKey = rs.getBytes(6);
long created = rs.getLong(5);
LocalAuthor localAuthor = new LocalAuthor(a, formatVersion, name,
publicKey, privateKey, created);
publicKey, privateKey, handshakePublicKey,
handshakePrivateKey, created);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
......@@ -3119,24 +3127,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public void setContactActive(Connection txn, ContactId c, boolean active)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE contacts SET active = ? WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setBoolean(1, active);
ps.setInt(2, c.getInt());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void setContactAlias(Connection txn, ContactId c,
@Nullable String alias) throws DbException {
......