Commit faba9a6b authored by akwizgran's avatar akwizgran

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

parent 891c82b2
......@@ -38,7 +38,18 @@ public interface IdentityManager {
/**
* Returns the cached local identity or loads it from the database.
* <p/>
* Read-only.
*/
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;
}
......@@ -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<Account> accounts = db.getAccounts(txn);
if (accounts.size() != 1) throw new DbException();
......
......@@ -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,20 +63,43 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
}
@Test
public void testOpenDatabaseHookNoAccountRegisteredHandshakeKeys()
throws Exception {
public void testOpenDatabaseHandshakeKeysGenerated() throws Exception {
context.checking(new Expectations() {{
oneOf(db).getAccounts(txn);
will(returnValue(singletonList(accountWithKeys)));
will(returnValue(singletonList(accountWithoutKeys)));
oneOf(crypto).generateAgreementKeyPair();
will(returnValue(handshakeKeyPair));
oneOf(handshakePublicKey).getEncoded();
will(returnValue(handshakePublicKeyBytes));
oneOf(handshakePrivateKey).getEncoded();
will(returnValue(handshakePrivateKeyBytes));
oneOf(db).setHandshakeKeyPair(txn, localAuthor.getId(),
handshakePublicKeyBytes, handshakePrivateKeyBytes);
}});
identityManager.onDatabaseOpened(txn);
}
@Test
public void testOpenDatabaseHookNoAccountRegisteredNoHandshakeKeys()
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();
......@@ -85,26 +108,19 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
will(returnValue(handshakePublicKeyBytes));
oneOf(handshakePrivateKey).getEncoded();
will(returnValue(handshakePrivateKeyBytes));
oneOf(db).setHandshakeKeyPair(txn, localAuthor.getId(),
handshakePublicKeyBytes, handshakePrivateKeyBytes);
}});
identityManager.onDatabaseOpened(txn);
assertEquals(localAuthor, identityManager.getLocalAuthor());
}
@Test
public void testGetLocalAuthor() throws Exception {
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());
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment