diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java index cfbdf1e68..6856fbbb8 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java @@ -38,7 +38,18 @@ public interface IdentityManager { /** * Returns the cached local identity or loads it from the database. + *

+ * Read-only. */ LocalAuthor getLocalAuthor(Transaction txn) throws DbException; + /** + * Returns the cached handshake keys or loads them from the database. + *

+ * Read-only. + * + * @return A two-element array containing the public key in the first + * element and the private key in the second + */ + byte[][] getHandshakeKeys(Transaction txn) throws DbException; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java index 3f2c0a6f9..e1662b570 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java @@ -21,6 +21,7 @@ import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.now; @@ -36,8 +37,27 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook { private final AuthorFactory authorFactory; private final Clock clock; + /** + * The user's account, or null if no account has been registered or loaded. + * If non-null, this account always has handshake keys. + */ @Nullable - private volatile Account cachedAccount; + private volatile Account cachedAccount = null; + + /** + * True if {@code cachedAccount} was registered via + * {@link #registerAccount(Account)} and should be stored when + * {@link #onDatabaseOpened(Transaction)} is called. + */ + + private volatile boolean shouldStoreAccount = false; + + /** + * True if the handshake keys in {@code cachedAccount} were generated when + * the account was loaded and should be stored when + * {@link #onDatabaseOpened(Transaction)} is called. + */ + private volatile boolean shouldStoreKeys = false; @Inject IdentityManagerImpl(DatabaseComponent db, CryptoComponent crypto, @@ -72,29 +92,24 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook { public void registerAccount(Account a) { if (!a.hasHandshakeKeyPair()) throw new IllegalArgumentException(); cachedAccount = a; + shouldStoreAccount = true; LOG.info("Account registered"); } @Override public void onDatabaseOpened(Transaction txn) throws DbException { Account cached = cachedAccount; - if (cached == null) { - cached = loadAccount(txn); - if (cached.hasHandshakeKeyPair()) { - cachedAccount = cached; - LOG.info("Account loaded"); - } else { - KeyPair handshakeKeyPair = crypto.generateAgreementKeyPair(); - byte[] handshakePub = handshakeKeyPair.getPublic().getEncoded(); - byte[] handshakePriv = - handshakeKeyPair.getPrivate().getEncoded(); - db.setHandshakeKeyPair(txn, cached.getId(), handshakePub, - handshakePriv); - LOG.info("Handshake key pair stored"); - } - } else { + if (cached == null) + cachedAccount = cached = loadAccountWithKeyPair(txn); + if (shouldStoreAccount) { db.addAccount(txn, cached); LOG.info("Account stored"); + } else if (shouldStoreKeys) { + requireNonNull(cached); + byte[] publicKey = requireNonNull(cached.getHandshakePublicKey()); + byte[] privateKey = requireNonNull(cached.getHandshakePrivateKey()); + db.setHandshakeKeyPair(txn, cached.getId(), publicKey, privateKey); + LOG.info("Handshake key pair stored"); } } @@ -102,9 +117,8 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook { public LocalAuthor getLocalAuthor() throws DbException { Account cached = cachedAccount; if (cached == null) { - cachedAccount = cached = - db.transactionWithResult(true, this::loadAccount); - LOG.info("Account loaded"); + cachedAccount = cached = db.transactionWithResult(true, + this::loadAccountWithKeyPair); } return cached.getLocalAuthor(); } @@ -112,13 +126,35 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook { @Override public LocalAuthor getLocalAuthor(Transaction txn) throws DbException { Account cached = cachedAccount; - if (cached == null) { - cachedAccount = cached = loadAccount(txn); - LOG.info("Account loaded"); - } + if (cached == null) + cachedAccount = cached = loadAccountWithKeyPair(txn); return cached.getLocalAuthor(); } + @Override + public byte[][] getHandshakeKeys(Transaction txn) throws DbException { + Account cached = cachedAccount; + if (cached == null) + cachedAccount = cached = loadAccountWithKeyPair(txn); + return new byte[][] { + cached.getHandshakePublicKey(), + cached.getHandshakePrivateKey() + }; + } + + private Account loadAccountWithKeyPair(Transaction txn) throws DbException { + Account a = loadAccount(txn); + LOG.info("Account loaded"); + if (a.hasHandshakeKeyPair()) return a; + KeyPair keyPair = crypto.generateAgreementKeyPair(); + byte[] publicKey = keyPair.getPublic().getEncoded(); + byte[] privateKey = keyPair.getPrivate().getEncoded(); + LOG.info("Handshake key pair generated"); + shouldStoreKeys = true; + return new Account(a.getLocalAuthor(), publicKey, privateKey, + a.getTimeCreated()); + } + private Account loadAccount(Transaction txn) throws DbException { Collection accounts = db.getAccounts(txn); if (accounts.size() != 1) throw new DbException(); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java index 76f5189c0..5333cba7e 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java @@ -53,7 +53,7 @@ public class IdentityManagerImplTest extends BrambleMockTestCase { } @Test - public void testOpenDatabaseHookAccountRegistered() throws Exception { + public void testOpenDatabaseAccountRegistered() throws Exception { context.checking(new Expectations() {{ oneOf(db).addAccount(txn, accountWithKeys); }}); @@ -63,19 +63,7 @@ public class IdentityManagerImplTest extends BrambleMockTestCase { } @Test - public void testOpenDatabaseHookNoAccountRegisteredHandshakeKeys() - throws Exception { - context.checking(new Expectations() {{ - oneOf(db).getAccounts(txn); - will(returnValue(singletonList(accountWithKeys))); - }}); - - identityManager.onDatabaseOpened(txn); - } - - @Test - public void testOpenDatabaseHookNoAccountRegisteredNoHandshakeKeys() - throws Exception { + public void testOpenDatabaseHandshakeKeysGenerated() throws Exception { context.checking(new Expectations() {{ oneOf(db).getAccounts(txn); will(returnValue(singletonList(accountWithoutKeys))); @@ -93,18 +81,46 @@ public class IdentityManagerImplTest extends BrambleMockTestCase { } @Test - public void testGetLocalAuthor() throws Exception { + public void testOpenDatabaseNoHandshakeKeysGenerated() throws Exception { + context.checking(new Expectations() {{ + oneOf(db).getAccounts(txn); + will(returnValue(singletonList(accountWithKeys))); + }}); + + identityManager.onDatabaseOpened(txn); + } + + @Test + public void testGetLocalAuthorAccountRegistered() throws DbException { + identityManager.registerAccount(accountWithKeys); + assertEquals(localAuthor, identityManager.getLocalAuthor()); + } + + @Test + public void testGetLocalAuthorHandshakeKeysGenerated() throws Exception { + context.checking(new DbExpectations() {{ + oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); + oneOf(db).getAccounts(txn); + will(returnValue(singletonList(accountWithoutKeys))); + oneOf(crypto).generateAgreementKeyPair(); + will(returnValue(handshakeKeyPair)); + oneOf(handshakePublicKey).getEncoded(); + will(returnValue(handshakePublicKeyBytes)); + oneOf(handshakePrivateKey).getEncoded(); + will(returnValue(handshakePrivateKeyBytes)); + }}); + + assertEquals(localAuthor, identityManager.getLocalAuthor()); + } + + @Test + public void testGetLocalAuthorNoHandshakeKeysGenerated() throws Exception { context.checking(new DbExpectations() {{ oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).getAccounts(txn); will(returnValue(singletonList(accountWithKeys))); }}); - assertEquals(localAuthor, identityManager.getLocalAuthor()); - } - @Test - public void testGetCachedLocalAuthor() throws DbException { - identityManager.registerAccount(accountWithKeys); assertEquals(localAuthor, identityManager.getLocalAuthor()); }