Generate handshake keys on demand, store when DB is opened.

This commit is contained in:
akwizgran
2019-04-26 17:49:07 +01:00
parent 891c82b2e5
commit faba9a6b70
3 changed files with 106 additions and 43 deletions

View File

@@ -38,7 +38,18 @@ public interface IdentityManager {
/** /**
* Returns the cached local identity or loads it from the database. * Returns the cached local identity or loads it from the database.
* <p/>
* Read-only.
*/ */
LocalAuthor getLocalAuthor(Transaction txn) throws DbException; LocalAuthor getLocalAuthor(Transaction txn) throws DbException;
/**
* Returns the cached handshake keys or loads them from the database.
* <p/>
* 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;
} }

View File

@@ -21,6 +21,7 @@ import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Logger.getLogger; 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.logDuration;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -36,8 +37,27 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
private final AuthorFactory authorFactory; private final AuthorFactory authorFactory;
private final Clock clock; 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 @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 @Inject
IdentityManagerImpl(DatabaseComponent db, CryptoComponent crypto, IdentityManagerImpl(DatabaseComponent db, CryptoComponent crypto,
@@ -72,29 +92,24 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
public void registerAccount(Account a) { public void registerAccount(Account a) {
if (!a.hasHandshakeKeyPair()) throw new IllegalArgumentException(); if (!a.hasHandshakeKeyPair()) throw new IllegalArgumentException();
cachedAccount = a; cachedAccount = a;
shouldStoreAccount = true;
LOG.info("Account registered"); LOG.info("Account registered");
} }
@Override @Override
public void onDatabaseOpened(Transaction txn) throws DbException { public void onDatabaseOpened(Transaction txn) throws DbException {
Account cached = cachedAccount; Account cached = cachedAccount;
if (cached == null) { if (cached == null)
cached = loadAccount(txn); cachedAccount = cached = loadAccountWithKeyPair(txn);
if (cached.hasHandshakeKeyPair()) { if (shouldStoreAccount) {
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 {
db.addAccount(txn, cached); db.addAccount(txn, cached);
LOG.info("Account stored"); 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 { public LocalAuthor getLocalAuthor() throws DbException {
Account cached = cachedAccount; Account cached = cachedAccount;
if (cached == null) { if (cached == null) {
cachedAccount = cached = cachedAccount = cached = db.transactionWithResult(true,
db.transactionWithResult(true, this::loadAccount); this::loadAccountWithKeyPair);
LOG.info("Account loaded");
} }
return cached.getLocalAuthor(); return cached.getLocalAuthor();
} }
@@ -112,13 +126,35 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
@Override @Override
public LocalAuthor getLocalAuthor(Transaction txn) throws DbException { public LocalAuthor getLocalAuthor(Transaction txn) throws DbException {
Account cached = cachedAccount; Account cached = cachedAccount;
if (cached == null) { if (cached == null)
cachedAccount = cached = loadAccount(txn); cachedAccount = cached = loadAccountWithKeyPair(txn);
LOG.info("Account loaded");
}
return cached.getLocalAuthor(); 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 { private Account loadAccount(Transaction txn) throws DbException {
Collection<Account> accounts = db.getAccounts(txn); Collection<Account> accounts = db.getAccounts(txn);
if (accounts.size() != 1) throw new DbException(); if (accounts.size() != 1) throw new DbException();

View File

@@ -53,7 +53,7 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testOpenDatabaseHookAccountRegistered() throws Exception { public void testOpenDatabaseAccountRegistered() throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).addAccount(txn, accountWithKeys); oneOf(db).addAccount(txn, accountWithKeys);
}}); }});
@@ -63,19 +63,7 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testOpenDatabaseHookNoAccountRegisteredHandshakeKeys() public void testOpenDatabaseHandshakeKeysGenerated() throws Exception {
throws Exception {
context.checking(new Expectations() {{
oneOf(db).getAccounts(txn);
will(returnValue(singletonList(accountWithKeys)));
}});
identityManager.onDatabaseOpened(txn);
}
@Test
public void testOpenDatabaseHookNoAccountRegisteredNoHandshakeKeys()
throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).getAccounts(txn); oneOf(db).getAccounts(txn);
will(returnValue(singletonList(accountWithoutKeys))); will(returnValue(singletonList(accountWithoutKeys)));
@@ -93,18 +81,46 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
} }
@Test @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() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getAccounts(txn); oneOf(db).getAccounts(txn);
will(returnValue(singletonList(accountWithKeys))); will(returnValue(singletonList(accountWithKeys)));
}}); }});
assertEquals(localAuthor, identityManager.getLocalAuthor());
}
@Test
public void testGetCachedLocalAuthor() throws DbException {
identityManager.registerAccount(accountWithKeys);
assertEquals(localAuthor, identityManager.getLocalAuthor()); assertEquals(localAuthor, identityManager.getLocalAuthor());
} }