mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 05:39:53 +01:00
Generate handshake keys on demand, store when DB is opened.
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user