Generate and store handshake keys at startup if needed.

This commit is contained in:
akwizgran
2019-04-22 17:55:20 +01:00
parent 8c315382e2
commit 8b2b7599f9
3 changed files with 117 additions and 38 deletions

View File

@@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -100,24 +101,32 @@ public class TestUtils {
} }
public static LocalAuthor getLocalAuthor() { public static LocalAuthor getLocalAuthor() {
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH)); return getLocalAuthor(false);
} }
public static LocalAuthor getLocalAuthor(int nameLength) { public static LocalAuthor getLocalAuthor(boolean withHandshakeKeys) {
AuthorId id = new AuthorId(getRandomId()); AuthorId id = new AuthorId(getRandomId());
int nameLength = 1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH);
String name = getRandomString(nameLength); String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey, if (withHandshakeKeys) {
timestamp); byte[] handshakePublicKey =
getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
byte[] handshakePrivateKey =
getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey,
privateKey, handshakePublicKey, handshakePrivateKey,
timestamp);
} else {
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey,
privateKey, timestamp);
}
} }
public static Author getAuthor() { public static Author getAuthor() {
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
}
public static Author getAuthor(int nameLength) {
AuthorId id = new AuthorId(getRandomId()); AuthorId id = new AuthorId(getRandomId());
int nameLength = 1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH);
String name = getRandomString(nameLength); String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new Author(id, FORMAT_VERSION, name, publicKey); return new Author(id, FORMAT_VERSION, name, publicKey);

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.identity; package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -9,6 +11,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook; import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -27,6 +30,7 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
getLogger(IdentityManagerImpl.class.getName()); getLogger(IdentityManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final CryptoComponent crypto;
private final AuthorFactory authorFactory; private final AuthorFactory authorFactory;
// The local author is immutable so we can cache it // The local author is immutable so we can cache it
@@ -34,8 +38,10 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
private volatile LocalAuthor cachedAuthor; private volatile LocalAuthor cachedAuthor;
@Inject @Inject
IdentityManagerImpl(DatabaseComponent db, AuthorFactory authorFactory) { IdentityManagerImpl(DatabaseComponent db, CryptoComponent crypto,
AuthorFactory authorFactory) {
this.db = db; this.db = db;
this.crypto = crypto;
this.authorFactory = authorFactory; this.authorFactory = authorFactory;
} }
@@ -57,38 +63,56 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
public void onDatabaseOpened(Transaction txn) throws DbException { public void onDatabaseOpened(Transaction txn) throws DbException {
LocalAuthor cached = cachedAuthor; LocalAuthor cached = cachedAuthor;
if (cached == null) { if (cached == null) {
LOG.info("No local author to store"); LocalAuthor loaded = loadLocalAuthor(txn);
return; if (loaded.getHandshakePublicKey() == null) {
KeyPair handshakeKeyPair = crypto.generateAgreementKeyPair();
byte[] handshakePublicKey =
handshakeKeyPair.getPublic().getEncoded();
byte[] handshakePrivateKey =
handshakeKeyPair.getPrivate().getEncoded();
db.setHandshakeKeyPair(txn, loaded.getId(),
handshakePublicKey, handshakePrivateKey);
cachedAuthor = new LocalAuthor(loaded.getId(),
loaded.getFormatVersion(), loaded.getName(),
loaded.getPublicKey(), loaded.getPrivateKey(),
handshakePublicKey, handshakePrivateKey,
loaded.getTimeCreated());
LOG.info("Handshake key pair added");
} else {
cachedAuthor = loaded;
LOG.info("Local author loaded");
}
} else {
db.addLocalAuthor(txn, cached);
LOG.info("Local author stored");
} }
db.addLocalAuthor(txn, cached);
LOG.info("Local author stored");
} }
@Override @Override
public LocalAuthor getLocalAuthor() throws DbException { public LocalAuthor getLocalAuthor() throws DbException {
if (cachedAuthor == null) { LocalAuthor cached = cachedAuthor;
cachedAuthor = if (cached == null) {
cachedAuthor = cached =
db.transactionWithResult(true, this::loadLocalAuthor); db.transactionWithResult(true, this::loadLocalAuthor);
LOG.info("Local author loaded"); LOG.info("Local author loaded");
} }
LocalAuthor cached = cachedAuthor;
if (cached == null) throw new AssertionError();
return cached; return cached;
} }
@Override @Override
public LocalAuthor getLocalAuthor(Transaction txn) throws DbException { public LocalAuthor getLocalAuthor(Transaction txn) throws DbException {
if (cachedAuthor == null) { LocalAuthor cached = cachedAuthor;
cachedAuthor = loadLocalAuthor(txn); if (cached == null) {
cachedAuthor = cached = loadLocalAuthor(txn);
LOG.info("Local author loaded"); LOG.info("Local author loaded");
} }
LocalAuthor cached = cachedAuthor;
if (cached == null) throw new AssertionError();
return cached; return cached;
} }
private LocalAuthor loadLocalAuthor(Transaction txn) throws DbException { private LocalAuthor loadLocalAuthor(Transaction txn) throws DbException {
return db.getLocalAuthors(txn).iterator().next(); Collection<LocalAuthor> authors = db.getLocalAuthors(txn);
if (authors.size() != 1) throw new IllegalStateException();
return authors.iterator().next();
} }
} }

View File

@@ -1,5 +1,9 @@
package org.briarproject.bramble.identity; package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -7,42 +11,48 @@ import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.Collection;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
public class IdentityManagerImplTest extends BrambleMockTestCase { public class IdentityManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final AuthorFactory authorFactory = private final AuthorFactory authorFactory =
context.mock(AuthorFactory.class); context.mock(AuthorFactory.class);
private final PublicKey handshakePublicKey = context.mock(PublicKey.class);
private final PrivateKey handshakePrivateKey =
context.mock(PrivateKey.class);
private final Transaction txn = new Transaction(null, false); private final Transaction txn = new Transaction(null, false);
private final LocalAuthor localAuthor = getLocalAuthor(); private final LocalAuthor localAuthor = getLocalAuthor(true);
private final Collection<LocalAuthor> localAuthors = private final LocalAuthor localAuthorWithoutHandshakeKeys =
singletonList(localAuthor); new LocalAuthor(localAuthor.getId(), localAuthor.getFormatVersion(),
localAuthor.getName(), localAuthor.getPublicKey(),
localAuthor.getPrivateKey(), localAuthor.getTimeCreated());
private final KeyPair handshakeKeyPair =
new KeyPair(handshakePublicKey, handshakePrivateKey);
private final byte[] handshakePublicKeyBytes =
localAuthor.getHandshakePublicKey();
private final byte[] handshakePrivateKeyBytes =
localAuthor.getHandshakePrivateKey();
private IdentityManagerImpl identityManager; private IdentityManagerImpl identityManager;
@Before @Before
public void setUp() { public void setUp() {
identityManager = new IdentityManagerImpl(db, authorFactory); identityManager = new IdentityManagerImpl(db, crypto, authorFactory);
} }
@Test @Test
public void testOpenDatabaseHookWithoutLocalAuthorRegistered() public void testOpenDatabaseHookLocalAuthorRegistered() throws Exception {
throws Exception { context.checking(new Expectations() {{
identityManager.onDatabaseOpened(txn);
}
@Test
public void testOpenDatabaseHookWithLocalAuthorRegistered()
throws Exception {
context.checking(new DbExpectations() {{
oneOf(db).addLocalAuthor(txn, localAuthor); oneOf(db).addLocalAuthor(txn, localAuthor);
}}); }});
@@ -50,12 +60,48 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
identityManager.onDatabaseOpened(txn); identityManager.onDatabaseOpened(txn);
} }
@Test
public void testOpenDatabaseHookNoLocalAuthorRegisteredHandshakeKeys()
throws Exception {
context.checking(new Expectations() {{
oneOf(db).getLocalAuthors(txn);
will(returnValue(singletonList(localAuthor)));
}});
identityManager.onDatabaseOpened(txn);
}
@Test
public void testOpenDatabaseHookNoLocalAuthorRegisteredNoHandshakeKeys()
throws Exception {
context.checking(new Expectations() {{
oneOf(db).getLocalAuthors(txn);
will(returnValue(singletonList(localAuthorWithoutHandshakeKeys)));
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);
LocalAuthor cached = identityManager.getLocalAuthor();
assertArrayEquals(handshakePublicKeyBytes,
cached.getHandshakePublicKey());
assertArrayEquals(handshakePrivateKeyBytes,
cached.getHandshakePrivateKey());
}
@Test @Test
public void testGetLocalAuthor() throws Exception { public void testGetLocalAuthor() 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).getLocalAuthors(txn); oneOf(db).getLocalAuthors(txn);
will(returnValue(localAuthors)); will(returnValue(singletonList(localAuthor)));
}}); }});
assertEquals(localAuthor, identityManager.getLocalAuthor()); assertEquals(localAuthor, identityManager.getLocalAuthor());
} }