diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java index 56a0694b0..821cfdc26 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java @@ -33,23 +33,18 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; -import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES; import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED; -import static org.briarproject.bramble.util.StringUtils.getRandomBase32String; import static org.briarproject.bramble.util.StringUtils.toUtf8; @ThreadSafe @NotNullByDefault class ContactManagerImpl implements ContactManager { - private static final String REMOTE_CONTACT_LINK = - "briar://" + getRandomBase32String(BASE32_LINK_BYTES); - private final DatabaseComponent db; private final KeyManager keyManager; private final IdentityManager identityManager; @@ -120,9 +115,10 @@ class ContactManagerImpl implements ContactManager { } @Override - public String getHandshakeLink() { - // TODO replace with real implementation - return REMOTE_CONTACT_LINK; + public String getHandshakeLink() throws DbException { + KeyPair keyPair = db.transactionWithResult(true, + identityManager::getHandshakeKeys); + return pendingContactFactory.createHandshakeLink(keyPair.getPublic()); } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactory.java index cfa555565..a5e0bc675 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactory.java @@ -3,6 +3,7 @@ package org.briarproject.bramble.contact; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.crypto.PublicKey; interface PendingContactFactory { @@ -15,4 +16,9 @@ interface PendingContactFactory { */ PendingContact createPendingContact(String link, String alias) throws FormatException; + + /** + * Creates a handshake link from the given public key. + */ + String createHandshakeLink(PublicKey k); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactoryImpl.java index ae9d1c0cb..68dd24cca 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactoryImpl.java @@ -20,6 +20,7 @@ import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES; +import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT; class PendingContactFactoryImpl implements PendingContactFactory { @@ -41,18 +42,31 @@ class PendingContactFactoryImpl implements PendingContactFactory { return new PendingContact(id, publicKey, alias, timestamp); } + @Override + public String createHandshakeLink(PublicKey k) { + if (!k.getKeyType().equals(KEY_TYPE_AGREEMENT)) + throw new IllegalArgumentException(); + byte[] encoded = k.getEncoded(); + if (encoded.length != RAW_LINK_BYTES - 1) + throw new IllegalArgumentException(); + byte[] raw = new byte[RAW_LINK_BYTES]; + raw[0] = FORMAT_VERSION; + arraycopy(encoded, 0, raw, 1, encoded.length); + return "briar://" + Base32.encode(raw).toLowerCase(); + } + private PublicKey parseHandshakeLink(String link) throws FormatException { Matcher matcher = LINK_REGEX.matcher(link); if (!matcher.find()) throw new FormatException(); // Discard 'briar://' and anything before or after the link link = matcher.group(2); - byte[] base32 = Base32.decode(link, false); - if (base32.length != RAW_LINK_BYTES) throw new AssertionError(); - byte version = base32[0]; + byte[] raw = Base32.decode(link, false); + if (raw.length != RAW_LINK_BYTES) throw new AssertionError(); + byte version = raw[0]; if (version != FORMAT_VERSION) throw new UnsupportedVersionException(version < FORMAT_VERSION); - byte[] publicKeyBytes = new byte[base32.length - 1]; - arraycopy(base32, 1, publicKeyBytes, 0, publicKeyBytes.length); + byte[] publicKeyBytes = new byte[raw.length - 1]; + arraycopy(raw, 1, publicKeyBytes, 0, publicKeyBytes.length); try { KeyParser parser = crypto.getAgreementKeyParser(); return parser.parsePublicKey(publicKeyBytes); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java index 195de4ef3..1eab88e88 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java @@ -3,6 +3,9 @@ package org.briarproject.bramble.contact; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; +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.crypto.SecretKey; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; @@ -17,7 +20,6 @@ import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.DbExpectations; import org.jmock.Expectations; -import org.jmock.Mockery; import org.junit.Test; import java.util.Collection; @@ -25,16 +27,20 @@ import java.util.Random; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED; +import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey; +import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getSecretKey; +import static org.briarproject.bramble.util.StringUtils.getRandomBase32String; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -42,7 +48,6 @@ import static org.junit.Assert.assertTrue; public class ContactManagerImplTest extends BrambleMockTestCase { - private final Mockery context = new Mockery(); private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final KeyManager keyManager = context.mock(KeyManager.class); private final IdentityManager identityManager = @@ -196,7 +201,6 @@ public class ContactManagerImplTest extends BrambleMockTestCase { Transaction txn = new Transaction(null, true); context.checking(new DbExpectations() {{ - oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(identityManager).getLocalAuthor(txn); will(returnValue(localAuthor)); oneOf(db).getContactsByAuthorId(txn, remote.getId()); @@ -258,4 +262,22 @@ public class ContactManagerImplTest extends BrambleMockTestCase { }}); } + @Test + public void testGetHandshakeLink() throws Exception { + Transaction txn = new Transaction(null, true); + PublicKey publicKey = getAgreementPublicKey(); + PrivateKey privateKey = getAgreementPrivateKey(); + KeyPair keyPair = new KeyPair(publicKey, privateKey); + String link = "briar://" + getRandomBase32String(BASE32_LINK_BYTES); + + context.checking(new DbExpectations() {{ + oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); + oneOf(identityManager).getHandshakeKeys(txn); + will(returnValue(keyPair)); + oneOf(pendingContactFactory).createHandshakeLink(publicKey); + will(returnValue(link)); + }}); + + assertEquals(link, contactManager.getHandshakeLink()); + } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/PendingContactFactoryImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/PendingContactFactoryImplTest.java index 39ee19324..879bd5db1 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/contact/PendingContactFactoryImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/PendingContactFactoryImplTest.java @@ -19,8 +19,12 @@ import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32 import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES; +import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT; +import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE; +import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.junit.Assert.assertArrayEquals; @@ -110,6 +114,57 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase { assertEquals(timestamp, p.getTimestamp()); } + @Test(expected = IllegalArgumentException.class) + public void testCreateHandshakeLinkRejectsInvalidKeyType() { + PublicKey invalidPublicKey = context.mock(PublicKey.class); + + context.checking(new Expectations() {{ + oneOf(invalidPublicKey).getKeyType(); + will(returnValue(KEY_TYPE_SIGNATURE)); + }}); + + pendingContactFactory.createHandshakeLink(invalidPublicKey); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateHandshakeLinkRejectsInvalidKeyLength() { + PublicKey invalidPublicKey = context.mock(PublicKey.class); + byte[] invalidPublicKeyBytes = + getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES + 1); + + context.checking(new Expectations() {{ + oneOf(invalidPublicKey).getKeyType(); + will(returnValue(KEY_TYPE_AGREEMENT)); + oneOf(invalidPublicKey).getEncoded(); + will(returnValue(invalidPublicKeyBytes)); + }}); + + pendingContactFactory.createHandshakeLink(invalidPublicKey); + } + + @Test + public void testCreateAndParseLink() throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).getAgreementKeyParser(); + will(returnValue(keyParser)); + oneOf(keyParser).parsePublicKey(publicKey.getEncoded()); + will(returnValue(publicKey)); + oneOf(crypto).hash(ID_LABEL, publicKey.getEncoded()); + will(returnValue(idBytes)); + oneOf(clock).currentTimeMillis(); + will(returnValue(timestamp)); + }}); + + String link = pendingContactFactory.createHandshakeLink(publicKey); + PendingContact p = + pendingContactFactory.createPendingContact(link, alias); + assertArrayEquals(idBytes, p.getId().getBytes()); + assertArrayEquals(publicKey.getEncoded(), + p.getPublicKey().getEncoded()); + assertEquals(alias, p.getAlias()); + assertEquals(timestamp, p.getTimestamp()); + } + private String encodeLink() { return encodeLink(FORMAT_VERSION); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java index 00a9da2a9..d51f1547b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java @@ -62,7 +62,7 @@ public class AddContactViewModel extends AndroidViewModel { handshakeLink.postValue(contactManager.getHandshakeLink()); } catch (DbException e) { logException(LOG, WARNING, e); - // the UI should stay disable in this case, + // the UI should stay disabled in this case, // leaving the user unable to proceed } });