From 677728b9ae81e9ff0b4179b1bfbce57718290b54 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 24 May 2019 14:16:38 +0100 Subject: [PATCH] Add database methods for converting a pending contact. --- .../bramble/api/contact/ContactManager.java | 19 ++++- .../bramble/api/db/DatabaseComponent.java | 7 ++ .../bramble/contact/ContactManagerImpl.java | 13 ++++ .../org/briarproject/bramble/db/Database.java | 11 ++- .../bramble/db/DatabaseComponentImpl.java | 22 ++++++ .../briarproject/bramble/db/JdbcDatabase.java | 42 ++++++++++ .../bramble/db/DatabaseComponentImplTest.java | 78 ++++++++++++++++++- .../bramble/db/JdbcDatabaseTest.java | 59 +++++++++++++- 8 files changed, 241 insertions(+), 10 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java index 2b18a17a5..16761d28d 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java @@ -32,12 +32,23 @@ public interface ContactManager { * derives and stores transport keys for each transport, and returns an ID * for the contact. * - * @param alice true if the local party is Alice + * @param alice True if the local party is Alice */ ContactId addContact(Transaction txn, Author remote, AuthorId local, SecretKey rootKey, long timestamp, boolean alice, boolean verified, boolean active) throws DbException; + /** + * Stores a contact associated with the given local and remote pseudonyms, + * replacing the given pending contact, derives and stores transport keys + * for each transport, and returns an ID for the contact. + * + * @param alice True if the local party is Alice + */ + ContactId addContact(Transaction txn, PendingContactId p, Author remote, + AuthorId local, SecretKey rootKey, long timestamp, boolean alice, + boolean verified, boolean active) throws DbException; + /** * Stores a contact associated with the given local and remote pseudonyms * and returns an ID for the contact. @@ -50,7 +61,7 @@ public interface ContactManager { * derives and stores transport keys for each transport, and returns an ID * for the contact. * - * @param alice true if the local party is Alice + * @param alice True if the local party is Alice */ ContactId addContact(Author remote, AuthorId local, SecretKey rootKey, long timestamp, boolean alice, boolean verified, boolean active) @@ -132,13 +143,13 @@ public interface ContactManager { void removeContact(Transaction txn, ContactId c) throws DbException; /** - * Sets an alias name for the contact or unsets it if alias is null. + * Sets an alias for the contact or unsets it if alias is null. */ void setContactAlias(Transaction txn, ContactId c, @Nullable String alias) throws DbException; /** - * Sets an alias name for the contact or unsets it if alias is null. + * Sets an alias for the contact or unsets it if alias is null. */ void setContactAlias(ContactId c, @Nullable String alias) throws DbException; diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java index a5e57224d..2112beb2b 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java @@ -63,6 +63,13 @@ public interface DatabaseComponent extends TransactionManager { ContactId addContact(Transaction txn, Author remote, AuthorId local, boolean verified) throws DbException; + /** + * Stores a contact associated with the given local and remote pseudonyms, + * replacing the given pending contact, and returns an ID for the contact. + */ + ContactId addContact(Transaction txn, PendingContactId p, Author remote, + AuthorId local, boolean verified) throws DbException; + /** * Stores a group. */ diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java index 510be77a0..24c09cef2 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java @@ -81,6 +81,19 @@ class ContactManagerImpl implements ContactManager { return c; } + @Override + public ContactId addContact(Transaction txn, PendingContactId p, + Author remote, AuthorId local, SecretKey rootKey, long timestamp, + boolean alice, boolean verified, boolean active) + throws DbException { + ContactId c = db.addContact(txn, p, remote, local, verified); + keyManager.addContactWithRotationKeys(txn, c, rootKey, timestamp, + alice, active); + Contact contact = db.getContact(txn, c); + for (ContactHook hook : hooks) hook.addingContact(txn, contact); + return c; + } + @Override public ContactId addContact(Transaction txn, Author remote, AuthorId local, boolean verified) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java index 7f7ad07c4..4a61a9360 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java @@ -630,7 +630,8 @@ interface Database { /** * Removes the given transport keys from the database. */ - void removeTransportKeys(T txn, TransportId t, KeySetId k) throws DbException; + void removeTransportKeys(T txn, TransportId t, KeySetId k) + throws DbException; /** * Resets the transmission count and expiry time of the given message with @@ -686,6 +687,14 @@ interface Database { void setTransportKeysActive(T txn, TransportId t, KeySetId k) throws DbException; + /** + * Transfers ownership of any transport keys from the given pending contact + * to the given contact and copies the pending contact's handshake public + * key to the contact. + */ + void transferKeys(T txn, PendingContactId p, ContactId c) + throws DbException; + /** * Updates the transmission count, expiry time and estimated time of arrival * of the given message with respect to the given contact, using the latency diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index 275d04147..246a131ce 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -248,6 +248,28 @@ class DatabaseComponentImpl implements DatabaseComponent { return c; } + @Override + public ContactId addContact(Transaction transaction, PendingContactId p, + Author remote, AuthorId local, boolean verified) + throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsPendingContact(txn, p)) + throw new NoSuchPendingContactException(); + if (!db.containsIdentity(txn, local)) + throw new NoSuchIdentityException(); + if (db.containsIdentity(txn, remote.getId())) + throw new ContactExistsException(local, remote); + if (db.containsContact(txn, remote.getId(), local)) + throw new ContactExistsException(local, remote); + ContactId c = db.addContact(txn, remote, local, verified); + db.transferKeys(txn, p, c); + db.removePendingContact(txn, p); + transaction.attach(new ContactAddedEvent(c)); + transaction.attach(new PendingContactRemovedEvent(p)); + return c; + } + @Override public void addGroup(Transaction transaction, Group g) throws DbException { if (transaction.isReadOnly()) throw new IllegalArgumentException(); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index 0a154d859..536eaf4e8 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -3115,6 +3115,48 @@ abstract class JdbcDatabase implements Database { } } + @Override + public void transferKeys(Connection txn, PendingContactId p, ContactId c) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + // Transfer the handshake public key + String sql = "SELECT publicKey from pendingContacts" + + " WHERE pendingContactId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, p.getBytes()); + rs = ps.executeQuery(); + if (!rs.next()) throw new DbStateException(); + byte[] publicKey = rs.getBytes(1); + if (rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + sql = "UPDATE contacts SET handshakePublicKey = ?" + + " WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, publicKey); + ps.setInt(2, c.getInt()); + int affected = ps.executeUpdate(); + if (affected < 0 || affected > 1) throw new DbStateException(); + ps.close(); + // Transfer the transport keys + sql = "UPDATE outgoingKeys" + + " SET contactId = ?, pendingContactId = NULL" + + " WHERE pendingContactId = ?"; + ps = txn.prepareStatement(sql); + ps.setInt(1, c.getInt()); + ps.setBytes(2, p.getBytes()); + affected = ps.executeUpdate(); + if (affected < 0) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(rs, LOG, WARNING); + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m, int maxLatency) throws DbException { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java index 24c363e22..ab634d036 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java @@ -763,16 +763,25 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { throws Exception { context.checking(new Expectations() {{ // Check whether the pending contact is in the DB (which it's not) - exactly(2).of(database).startTransaction(); + exactly(3).of(database).startTransaction(); will(returnValue(txn)); - exactly(2).of(database).containsPendingContact(txn, + exactly(3).of(database).containsPendingContact(txn, pendingContactId); will(returnValue(false)); - exactly(2).of(database).abortTransaction(txn); + exactly(3).of(database).abortTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, eventBus, eventExecutor, shutdownManager); + try { + db.transaction(false, transaction -> + db.addContact(transaction, pendingContactId, author, + localAuthor.getId(), true)); + fail(); + } catch (NoSuchPendingContactException expected) { + // Expected + } + try { db.transaction(false, transaction -> db.addTransportKeys(transaction, pendingContactId, @@ -1494,6 +1503,69 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { } } + @Test + public void testCannotAddLocalIdentityAsContactFromPendingContact() + throws Exception { + context.checking(new Expectations() {{ + oneOf(database).startTransaction(); + will(returnValue(txn)); + oneOf(database).containsPendingContact(txn, pendingContactId); + will(returnValue(true)); + oneOf(database).containsIdentity(txn, localAuthor.getId()); + will(returnValue(true)); + // Contact is a local identity + oneOf(database).containsIdentity(txn, author.getId()); + will(returnValue(true)); + oneOf(database).abortTransaction(txn); + }}); + + DatabaseComponent db = createDatabaseComponent(database, eventBus, + eventExecutor, shutdownManager); + + try { + db.transaction(false, transaction -> + db.addContact(transaction, pendingContactId, author, + localAuthor.getId(), true)); + fail(); + } catch (ContactExistsException expected) { + assertEquals(localAuthor.getId(), expected.getLocalAuthorId()); + assertEquals(author, expected.getRemoteAuthor()); + } + } + + @Test + public void testCannotAddDuplicateContactFromPendingContact() + throws Exception { + context.checking(new Expectations() {{ + oneOf(database).startTransaction(); + will(returnValue(txn)); + oneOf(database).containsPendingContact(txn, pendingContactId); + will(returnValue(true)); + oneOf(database).containsIdentity(txn, localAuthor.getId()); + will(returnValue(true)); + oneOf(database).containsIdentity(txn, author.getId()); + will(returnValue(false)); + // Contact already exists for this local identity + oneOf(database).containsContact(txn, author.getId(), + localAuthor.getId()); + will(returnValue(true)); + oneOf(database).abortTransaction(txn); + }}); + + DatabaseComponent db = createDatabaseComponent(database, eventBus, + eventExecutor, shutdownManager); + + try { + db.transaction(false, transaction -> + db.addContact(transaction, pendingContactId, author, + localAuthor.getId(), true)); + fail(); + } catch (ContactExistsException expected) { + assertEquals(localAuthor.getId(), expected.getLocalAuthorId()); + assertEquals(author, expected.getRemoteAuthor()); + } + } + @Test public void testMessageDependencies() throws Exception { int shutdownHandle = 12345; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java index 2fc0ab5b1..4bb66f114 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java @@ -2204,12 +2204,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { assertEquals(emptyList(), db.getPendingContacts(txn)); db.addPendingContact(txn, pendingContact); - Collection pendingContacts = - db.getPendingContacts(txn); + Collection pendingContacts = db.getPendingContacts(txn); assertEquals(1, pendingContacts.size()); PendingContact retrieved = pendingContacts.iterator().next(); assertEquals(pendingContact.getId(), retrieved.getId()); assertEquals(pendingContact.getAlias(), retrieved.getAlias()); + assertArrayEquals(pendingContact.getPublicKey().getEncoded(), + retrieved.getPublicKey().getEncoded()); assertEquals(pendingContact.getTimestamp(), retrieved.getTimestamp()); db.removePendingContact(txn, pendingContact.getId()); @@ -2219,6 +2220,60 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { db.close(); } + @Test + public void testTransferKeys() throws Exception { + boolean alice = random.nextBoolean(); + TransportKeys transportKeys = + createHandshakeKeys(1000, getSecretKey(), alice); + + Database db = open(false); + Connection txn = db.startTransaction(); + + // Add the pending contact, the transport and the handshake keys + db.addPendingContact(txn, pendingContact); + db.addTransport(txn, transportId, 123); + assertEquals(keySetId, db.addTransportKeys(txn, pendingContact.getId(), + transportKeys)); + + Collection allKeys = + db.getTransportKeys(txn, transportId); + assertEquals(1, allKeys.size()); + TransportKeySet ks = allKeys.iterator().next(); + assertEquals(keySetId, ks.getKeySetId()); + assertNull(ks.getContactId()); + assertEquals(pendingContact.getId(), ks.getPendingContactId()); + + // Add a contact + db.addIdentity(txn, identity); + assertEquals(contactId, + db.addContact(txn, author, localAuthor.getId(), true)); + + // The contact shouldn't have a handshake public key + Contact contact = db.getContact(txn, contactId); + assertNull(contact.getHandshakePublicKey()); + + // Transfer the keys to the contact + db.transferKeys(txn, pendingContact.getId(), contactId); + + // The handshake public key should have been copied to the contact + contact = db.getContact(txn, contactId); + PublicKey handshakePublicKey = contact.getHandshakePublicKey(); + assertNotNull(handshakePublicKey); + assertArrayEquals(pendingContact.getPublicKey().getEncoded(), + handshakePublicKey.getEncoded()); + + // The transport keys should have been transferred to the contact + allKeys = db.getTransportKeys(txn, transportId); + assertEquals(1, allKeys.size()); + ks = allKeys.iterator().next(); + assertEquals(keySetId, ks.getKeySetId()); + assertEquals(contactId, ks.getContactId()); + assertNull(ks.getPendingContactId()); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testSetHandshakeKeyPair() throws Exception { Identity withoutKeys = new Identity(localAuthor, null, null,