Add contact and transport keys in the same transaction.

This avoids a potential problem where the app crashes after adding the contact but before adding the transport keys, leaving the contact unusable.
This commit is contained in:
akwizgran
2016-02-26 10:57:32 +00:00
parent 8c8b2a5358
commit 1d89c6cebc
9 changed files with 37 additions and 50 deletions

View File

@@ -1,5 +1,6 @@
package org.briarproject.api.contact; package org.briarproject.api.contact;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
@@ -19,8 +20,8 @@ public interface ContactManager {
* Stores a contact associated with the given local and remote pseudonyms, * Stores a contact associated with the given local and remote pseudonyms,
* and returns an ID for the contact. * and returns an ID for the contact.
*/ */
ContactId addContact(Author remote, AuthorId local, boolean active) ContactId addContact(Author remote, AuthorId local, SecretKey master,
throws DbException; long timestamp, boolean alice, boolean active) throws DbException;
/** Returns the contact with the given ID. */ /** Returns the contact with the given ID. */
Contact getContact(ContactId c) throws DbException; Contact getContact(ContactId c) throws DbException;

View File

@@ -3,6 +3,8 @@ package org.briarproject.api.transport;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
/** /**
* Responsible for managing transport keys and recognising the pseudo-random * Responsible for managing transport keys and recognising the pseudo-random
@@ -16,8 +18,8 @@ public interface KeyManager {
* {@link StreamContext StreamContexts} for the contact can be created * {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned. * after this method has returned.
*/ */
void addContact(ContactId c, SecretKey master, long timestamp, void addContact(Transaction txn, ContactId c, SecretKey master,
boolean alice); long timestamp, boolean alice) throws DbException;
/** /**
* Returns a {@link StreamContext} for sending a stream to the given * Returns a {@link StreamContext} for sending a stream to the given

View File

@@ -5,6 +5,7 @@ import com.google.inject.Inject;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager; import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction; import org.briarproject.api.db.Transaction;
@@ -12,6 +13,7 @@ import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager.RemoveIdentityHook; import org.briarproject.api.identity.IdentityManager.RemoveIdentityHook;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.transport.KeyManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -22,12 +24,14 @@ import java.util.concurrent.CopyOnWriteArrayList;
class ContactManagerImpl implements ContactManager, RemoveIdentityHook { class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
private final DatabaseComponent db; private final DatabaseComponent db;
private final KeyManager keyManager;
private final List<AddContactHook> addHooks; private final List<AddContactHook> addHooks;
private final List<RemoveContactHook> removeHooks; private final List<RemoveContactHook> removeHooks;
@Inject @Inject
ContactManagerImpl(DatabaseComponent db) { ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) {
this.db = db; this.db = db;
this.keyManager = keyManager;
addHooks = new CopyOnWriteArrayList<AddContactHook>(); addHooks = new CopyOnWriteArrayList<AddContactHook>();
removeHooks = new CopyOnWriteArrayList<RemoveContactHook>(); removeHooks = new CopyOnWriteArrayList<RemoveContactHook>();
} }
@@ -43,12 +47,14 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
} }
@Override @Override
public ContactId addContact(Author remote, AuthorId local, boolean active) public ContactId addContact(Author remote, AuthorId local, SecretKey master,
long timestamp, boolean alice, boolean active)
throws DbException { throws DbException {
ContactId c; ContactId c;
Transaction txn = db.startTransaction(); Transaction txn = db.startTransaction();
try { try {
c = db.addContact(txn, remote, local, active); c = db.addContact(txn, remote, local, active);
keyManager.addContact(txn, c, master, timestamp, alice);
Contact contact = db.getContact(txn, c); Contact contact = db.getContact(txn, c);
for (AddContactHook hook : addHooks) for (AddContactHook hook : addHooks)
hook.addingContact(txn, contact); hook.addingContact(txn, contact);

View File

@@ -219,9 +219,7 @@ abstract class Connector extends Thread {
long timestamp, boolean alice) throws DbException { long timestamp, boolean alice) throws DbException {
// Add the contact to the database // Add the contact to the database
contactId = contactManager.addContact(remoteAuthor, contactId = contactManager.addContact(remoteAuthor,
localAuthor.getId(), true); localAuthor.getId(), master, timestamp, alice, true);
// Derive transport keys
keyManager.addContact(contactId, master, timestamp, alice);
return contactId; return contactId;
} }

View File

@@ -88,10 +88,10 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
return true; return true;
} }
public void addContact(ContactId c, SecretKey master, long timestamp, public void addContact(Transaction txn, ContactId c, SecretKey master,
boolean alice) { long timestamp, boolean alice) throws DbException {
for (TransportKeyManager m : managers.values()) for (TransportKeyManager m : managers.values())
m.addContact(c, master, timestamp, alice); m.addContact(txn, c, master, timestamp, alice);
} }
public StreamContext getStreamContext(ContactId c, TransportId t) { public StreamContext getStreamContext(ContactId c, TransportId t) {

View File

@@ -149,8 +149,8 @@ class TransportKeyManager {
timer.schedule(task, delay); timer.schedule(task, delay);
} }
void addContact(ContactId c, SecretKey master, long timestamp, void addContact(Transaction txn, ContactId c, SecretKey master,
boolean alice) { long timestamp, boolean alice) throws DbException {
lock.lock(); lock.lock();
try { try {
// Work out what rotation period the timestamp belongs to // Work out what rotation period the timestamp belongs to
@@ -164,15 +164,7 @@ class TransportKeyManager {
// Initialise mutable state for the contact // Initialise mutable state for the contact
addKeys(c, new MutableTransportKeys(k)); addKeys(c, new MutableTransportKeys(k));
// Write the keys back to the DB // Write the keys back to the DB
Transaction txn = db.startTransaction(); db.addTransportKeys(txn, c, k);
try {
db.addTransportKeys(txn, c, k);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally { } finally {
lock.unlock(); lock.unlock();
} }

View File

@@ -31,6 +31,7 @@ import org.briarproject.event.EventModule;
import org.briarproject.forum.ForumModule; import org.briarproject.forum.ForumModule;
import org.briarproject.identity.IdentityModule; import org.briarproject.identity.IdentityModule;
import org.briarproject.messaging.MessagingModule; import org.briarproject.messaging.MessagingModule;
import org.briarproject.transport.TransportModule;
import org.junit.Test; import org.junit.Test;
import java.util.Random; import java.util.Random;
@@ -57,7 +58,8 @@ public class ConstantsTest extends BriarTestCase {
new TestLifecycleModule(), new TestSystemModule(), new TestLifecycleModule(), new TestSystemModule(),
new ContactModule(), new CryptoModule(), new DatabaseModule(), new ContactModule(), new CryptoModule(), new DatabaseModule(),
new DataModule(), new EventModule(), new ForumModule(), new DataModule(), new EventModule(), new ForumModule(),
new IdentityModule(), new MessagingModule(), new SyncModule()); new IdentityModule(), new MessagingModule(), new SyncModule(),
new TransportModule());
crypto = i.getInstance(CryptoComponent.class); crypto = i.getInstance(CryptoComponent.class);
authorFactory = i.getInstance(AuthorFactory.class); authorFactory = i.getInstance(AuthorFactory.class);
privateMessageFactory = i.getInstance(PrivateMessageFactory.class); privateMessageFactory = i.getInstance(PrivateMessageFactory.class);

View File

@@ -135,9 +135,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
Author bobAuthor = new Author(bobId, "Bob", Author bobAuthor = new Author(bobId, "Bob",
new byte[MAX_PUBLIC_KEY_LENGTH]); new byte[MAX_PUBLIC_KEY_LENGTH]);
ContactId contactId = contactManager.addContact(bobAuthor, aliceId, ContactId contactId = contactManager.addContact(bobAuthor, aliceId,
true); master, timestamp, true, true);
// Derive and store the transport keys
keyManager.addContact(contactId, master, timestamp, true);
// Send Bob a message // Send Bob a message
GroupId groupId = messagingManager.getConversationId(contactId); GroupId groupId = messagingManager.getConversationId(contactId);
@@ -206,9 +204,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
Author aliceAuthor = new Author(aliceId, "Alice", Author aliceAuthor = new Author(aliceId, "Alice",
new byte[MAX_PUBLIC_KEY_LENGTH]); new byte[MAX_PUBLIC_KEY_LENGTH]);
ContactId contactId = contactManager.addContact(aliceAuthor, bobId, ContactId contactId = contactManager.addContact(aliceAuthor, bobId,
true); master, timestamp, false, true);
// Derive and store the transport keys
keyManager.addContact(contactId, master, timestamp, false);
// Set up an event listener // Set up an event listener
MessageListener listener = new MessageListener(); MessageListener listener = new MessageListener();

View File

@@ -133,17 +133,15 @@ public class TransportKeyManagerTest extends BriarTestCase {
will(new EncodeTagAction()); will(new EncodeTagAction());
} }
// Save the keys // Save the keys
oneOf(db).startTransaction();
will(returnValue(txn));
oneOf(db).addTransportKeys(txn, contactId, rotated); oneOf(db).addTransportKeys(txn, contactId, rotated);
oneOf(db).endTransaction(txn);
}}); }});
TransportKeyManager transportKeyManager = new TransportKeyManager(db, TransportKeyManager transportKeyManager = new TransportKeyManager(db,
crypto, timer, clock, transportId, maxLatency); crypto, timer, clock, transportId, maxLatency);
// The timestamp is 1 ms before the start of rotation period 1000 // The timestamp is 1 ms before the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000 - 1; long timestamp = rotationPeriodLength * 1000 - 1;
transportKeyManager.addContact(contactId, masterKey, timestamp, alice); transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -194,17 +192,15 @@ public class TransportKeyManagerTest extends BriarTestCase {
oneOf(crypto).rotateTransportKeys(transportKeys, 1000); oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Save the keys // Save the keys
oneOf(db).startTransaction();
will(returnValue(txn));
oneOf(db).addTransportKeys(txn, contactId, transportKeys); oneOf(db).addTransportKeys(txn, contactId, transportKeys);
oneOf(db).endTransaction(txn);
}}); }});
TransportKeyManager transportKeyManager = new TransportKeyManager(db, TransportKeyManager transportKeyManager = new TransportKeyManager(db,
crypto, timer, clock, transportId, maxLatency); crypto, timer, clock, transportId, maxLatency);
// The timestamp is at the start of rotation period 1000 // The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000; long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(contactId, masterKey, timestamp, alice); transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
assertNull(transportKeyManager.getStreamContext(contactId)); assertNull(transportKeyManager.getStreamContext(contactId));
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -240,10 +236,7 @@ public class TransportKeyManagerTest extends BriarTestCase {
oneOf(crypto).rotateTransportKeys(transportKeys, 1000); oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Save the keys // Save the keys
oneOf(db).startTransaction();
will(returnValue(txn));
oneOf(db).addTransportKeys(txn, contactId, transportKeys); oneOf(db).addTransportKeys(txn, contactId, transportKeys);
oneOf(db).endTransaction(txn);
// Increment the stream counter // Increment the stream counter
oneOf(db).startTransaction(); oneOf(db).startTransaction();
will(returnValue(txn1)); will(returnValue(txn1));
@@ -256,7 +249,8 @@ public class TransportKeyManagerTest extends BriarTestCase {
crypto, timer, clock, transportId, maxLatency); crypto, timer, clock, transportId, maxLatency);
// The timestamp is at the start of rotation period 1000 // The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000; long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(contactId, masterKey, timestamp, alice); transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
// The first request should return a stream context // The first request should return a stream context
StreamContext ctx = transportKeyManager.getStreamContext(contactId); StreamContext ctx = transportKeyManager.getStreamContext(contactId);
assertNotNull(ctx); assertNotNull(ctx);
@@ -299,17 +293,15 @@ public class TransportKeyManagerTest extends BriarTestCase {
oneOf(crypto).rotateTransportKeys(transportKeys, 1000); oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Save the keys // Save the keys
oneOf(db).startTransaction();
will(returnValue(txn));
oneOf(db).addTransportKeys(txn, contactId, transportKeys); oneOf(db).addTransportKeys(txn, contactId, transportKeys);
oneOf(db).endTransaction(txn);
}}); }});
TransportKeyManager transportKeyManager = new TransportKeyManager(db, TransportKeyManager transportKeyManager = new TransportKeyManager(db,
crypto, timer, clock, transportId, maxLatency); crypto, timer, clock, transportId, maxLatency);
// The timestamp is at the start of rotation period 1000 // The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000; long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(contactId, masterKey, timestamp, alice); transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
assertNull(transportKeyManager.getStreamContext(new byte[TAG_LENGTH])); assertNull(transportKeyManager.getStreamContext(new byte[TAG_LENGTH]));
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -345,10 +337,7 @@ public class TransportKeyManagerTest extends BriarTestCase {
oneOf(crypto).rotateTransportKeys(transportKeys, 1000); oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys)); will(returnValue(transportKeys));
// Save the keys // Save the keys
oneOf(db).startTransaction();
will(returnValue(txn));
oneOf(db).addTransportKeys(txn, contactId, transportKeys); oneOf(db).addTransportKeys(txn, contactId, transportKeys);
oneOf(db).endTransaction(txn);
// Encode a new tag after sliding the window // Encode a new tag after sliding the window
oneOf(crypto).encodeTag(with(any(byte[].class)), oneOf(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with((long) REORDERING_WINDOW_SIZE)); with(tagKey), with((long) REORDERING_WINDOW_SIZE));
@@ -365,7 +354,8 @@ public class TransportKeyManagerTest extends BriarTestCase {
crypto, timer, clock, transportId, maxLatency); crypto, timer, clock, transportId, maxLatency);
// The timestamp is at the start of rotation period 1000 // The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000; long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(contactId, masterKey, timestamp, alice); transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
// Use the first tag (previous rotation period, stream number 0) // Use the first tag (previous rotation period, stream number 0)
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size()); assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
byte[] tag = tags.get(0); byte[] tag = tags.get(0);