mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-22 23:59:54 +01:00
Merge branch '1537-contact-manager-pending-contacts' into 'master'
Implement contact manager methods for pending contacts Closes #1537 See merge request briar/briar!1081
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
package org.briarproject.bramble.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when data being parsed uses a protocol or format version that is not
|
||||||
|
* supported.
|
||||||
|
*/
|
||||||
|
public class UnsupportedVersionException extends FormatException {
|
||||||
|
|
||||||
|
private final boolean tooOld;
|
||||||
|
|
||||||
|
public UnsupportedVersionException(boolean tooOld) {
|
||||||
|
this.tooOld = tooOld;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTooOld() {
|
||||||
|
return tooOld;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package org.briarproject.bramble.api.contact;
|
package org.briarproject.bramble.api.contact;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
@@ -11,17 +13,12 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface ContactManager {
|
public interface ContactManager {
|
||||||
|
|
||||||
int LINK_LENGTH = 64;
|
|
||||||
Pattern LINK_REGEX =
|
|
||||||
Pattern.compile("(briar://)?([a-z2-7]{" + LINK_LENGTH + "})");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a hook to be called whenever a contact is added or removed.
|
* Registers a hook to be called whenever a contact is added or removed.
|
||||||
* This method should be called before
|
* This method should be called before
|
||||||
@@ -59,17 +56,23 @@ public interface ContactManager {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the static link that needs to be sent to the contact to be added.
|
* Returns the handshake link that needs to be sent to a contact we want
|
||||||
|
* to add.
|
||||||
*/
|
*/
|
||||||
String getHandshakeLink() throws DbException;
|
String getHandshakeLink() throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests a new contact to be added via the given {@code link}.
|
* Creates a {@link PendingContact} from the given handshake link and
|
||||||
|
* alias, adds it to the database and returns it.
|
||||||
*
|
*
|
||||||
* @param link The link received from the contact we want to add.
|
* @param link The handshake link received from the contact we want to add
|
||||||
* @param alias The alias the user has given this contact.
|
* @param alias The alias the user has given this contact
|
||||||
|
* @return A PendingContact representing the contact to be added
|
||||||
|
* @throws UnsupportedVersionException If the link uses a format version
|
||||||
|
* that is not supported
|
||||||
|
* @throws FormatException If the link is invalid
|
||||||
*/
|
*/
|
||||||
void addPendingContact(String link, String alias)
|
PendingContact addPendingContact(String link, String alias)
|
||||||
throws DbException, FormatException;
|
throws DbException, FormatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,10 +81,9 @@ public interface ContactManager {
|
|||||||
Collection<PendingContact> getPendingContacts() throws DbException;
|
Collection<PendingContact> getPendingContacts() throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a {@link PendingContact} that is in state
|
* Removes a {@link PendingContact}.
|
||||||
* {@link PendingContactState FAILED}.
|
|
||||||
*/
|
*/
|
||||||
void removePendingContact(PendingContactId pendingContact) throws DbException;
|
void removePendingContact(PendingContactId p) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the contact with the given ID.
|
* Returns the contact with the given ID.
|
||||||
@@ -92,7 +94,7 @@ public interface ContactManager {
|
|||||||
* Returns the contact with the given remoteAuthorId
|
* Returns the contact with the given remoteAuthorId
|
||||||
* that was added by the LocalAuthor with the given localAuthorId
|
* that was added by the LocalAuthor with the given localAuthorId
|
||||||
*
|
*
|
||||||
* @throws org.briarproject.bramble.api.db.NoSuchContactException
|
* @throws NoSuchContactException If the contact is not in the database
|
||||||
*/
|
*/
|
||||||
Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
@@ -101,7 +103,7 @@ public interface ContactManager {
|
|||||||
* Returns the contact with the given remoteAuthorId
|
* Returns the contact with the given remoteAuthorId
|
||||||
* that was added by the LocalAuthor with the given localAuthorId
|
* that was added by the LocalAuthor with the given localAuthorId
|
||||||
*
|
*
|
||||||
* @throws org.briarproject.bramble.api.db.NoSuchContactException
|
* @throws NoSuchContactException If the contact is not in the database
|
||||||
*/
|
*/
|
||||||
Contact getContact(Transaction txn, AuthorId remoteAuthorId,
|
Contact getContact(Transaction txn, AuthorId remoteAuthorId,
|
||||||
AuthorId localAuthorId) throws DbException;
|
AuthorId localAuthorId) throws DbException;
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.bramble.api.contact;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public interface HandshakeLinkConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current version of the handshake link format.
|
||||||
|
*/
|
||||||
|
int FORMAT_VERSION = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of a base32-encoded handshake link in bytes, excluding the
|
||||||
|
* 'briar://' prefix.
|
||||||
|
*/
|
||||||
|
int BASE32_LINK_BYTES = 53;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of a raw handshake link in bytes, before base32 encoding.
|
||||||
|
*/
|
||||||
|
int RAW_LINK_BYTES = 33;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression for matching handshake links, including or excluding
|
||||||
|
* the 'briar://' prefix.
|
||||||
|
*/
|
||||||
|
Pattern LINK_REGEX =
|
||||||
|
Pattern.compile("(briar://)?([a-z2-7]{" + BASE32_LINK_BYTES + "})");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for hashing handshake public keys to calculate their identifiers.
|
||||||
|
*/
|
||||||
|
String ID_LABEL = "org.briarproject.bramble/HANDSHAKE_KEY_ID";
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.keyagreement;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when a QR code that has been scanned uses a protocol version that is
|
|
||||||
* not supported.
|
|
||||||
*/
|
|
||||||
public class UnsupportedVersionException extends IOException {
|
|
||||||
|
|
||||||
private final boolean tooOld;
|
|
||||||
|
|
||||||
public UnsupportedVersionException(boolean tooOld) {
|
|
||||||
this.tooOld = tooOld;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTooOld() {
|
|
||||||
return tooOld;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package org.briarproject.bramble.util;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
public class Base32 {
|
||||||
|
|
||||||
|
private static final char[] DIGITS = {
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
|
||||||
|
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
|
'Y', 'Z', '2', '3', '4', '5', '6', '7'
|
||||||
|
};
|
||||||
|
|
||||||
|
public static String encode(byte[] b) {
|
||||||
|
StringBuilder s = new StringBuilder();
|
||||||
|
int byteIndex = 0, currentCode = 0x00;
|
||||||
|
int byteMask = 0x80, codeMask = 0x10;
|
||||||
|
while (byteIndex < b.length) {
|
||||||
|
if ((b[byteIndex] & byteMask) != 0) currentCode |= codeMask;
|
||||||
|
// After every 8 bits, move on to the next byte
|
||||||
|
if (byteMask == 0x01) {
|
||||||
|
byteMask = 0x80;
|
||||||
|
byteIndex++;
|
||||||
|
} else {
|
||||||
|
byteMask >>>= 1;
|
||||||
|
}
|
||||||
|
// After every 5 bits, move on to the next digit
|
||||||
|
if (codeMask == 0x01) {
|
||||||
|
s.append(DIGITS[currentCode]);
|
||||||
|
codeMask = 0x10;
|
||||||
|
currentCode = 0x00;
|
||||||
|
} else {
|
||||||
|
codeMask >>>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we're part-way through a digit, output it
|
||||||
|
if (codeMask != 0x10) s.append(DIGITS[currentCode]);
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] decode(String s, boolean strict) {
|
||||||
|
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||||
|
int digitIndex = 0, digitCount = s.length(), currentByte = 0x00;
|
||||||
|
int byteMask = 0x80, codeMask = 0x10;
|
||||||
|
while (digitIndex < digitCount) {
|
||||||
|
int code = decodeDigit(s.charAt(digitIndex));
|
||||||
|
if ((code & codeMask) != 0) currentByte |= byteMask;
|
||||||
|
// After every 8 bits, move on to the next byte
|
||||||
|
if (byteMask == 0x01) {
|
||||||
|
b.write(currentByte);
|
||||||
|
byteMask = 0x80;
|
||||||
|
currentByte = 0x00;
|
||||||
|
} else {
|
||||||
|
byteMask >>>= 1;
|
||||||
|
}
|
||||||
|
// After every 5 bits, move on to the next digit
|
||||||
|
if (codeMask == 0x01) {
|
||||||
|
codeMask = 0x10;
|
||||||
|
digitIndex++;
|
||||||
|
} else {
|
||||||
|
codeMask >>>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If any extra bits were used for encoding, they should all be zero
|
||||||
|
if (strict && byteMask != 0x80 && currentByte != 0x00)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
return b.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int decodeDigit(char c) {
|
||||||
|
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||||
|
if (c >= 'a' && c <= 'z') return c - 'a';
|
||||||
|
if (c >= '2' && c <= '7') return c - '2' + 26;
|
||||||
|
throw new IllegalArgumentException("Not a base32 digit: " + c);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -153,4 +153,13 @@ public class StringUtils {
|
|||||||
return new String(c);
|
return new String(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getRandomBase32String(int length) {
|
||||||
|
char[] c = new char[length];
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int character = random.nextInt(32);
|
||||||
|
if (character < 26) c[i] = (char) ('a' + character);
|
||||||
|
else c[i] = (char) ('2' + (character - 26));
|
||||||
|
}
|
||||||
|
return new String(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.contact;
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
@@ -20,19 +21,19 @@ import org.briarproject.bramble.api.transport.KeyManager;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
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.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
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.UNKNOWN;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
|
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.api.identity.AuthorInfo.Status.VERIFIED;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
|
||||||
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@@ -40,19 +41,22 @@ import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
|||||||
class ContactManagerImpl implements ContactManager {
|
class ContactManagerImpl implements ContactManager {
|
||||||
|
|
||||||
private static final String REMOTE_CONTACT_LINK =
|
private static final String REMOTE_CONTACT_LINK =
|
||||||
"briar://" + getRandomBase32String(LINK_LENGTH);
|
"briar://" + getRandomBase32String(BASE32_LINK_BYTES);
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final KeyManager keyManager;
|
private final KeyManager keyManager;
|
||||||
private final IdentityManager identityManager;
|
private final IdentityManager identityManager;
|
||||||
|
private final PendingContactFactory pendingContactFactory;
|
||||||
private final List<ContactHook> hooks;
|
private final List<ContactHook> hooks;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager,
|
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager,
|
||||||
IdentityManager identityManager) {
|
IdentityManager identityManager,
|
||||||
|
PendingContactFactory pendingContactFactory) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.keyManager = keyManager;
|
this.keyManager = keyManager;
|
||||||
this.identityManager = identityManager;
|
this.identityManager = identityManager;
|
||||||
|
this.pendingContactFactory = pendingContactFactory;
|
||||||
hooks = new CopyOnWriteArrayList<>();
|
hooks = new CopyOnWriteArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,34 +100,23 @@ class ContactManagerImpl implements ContactManager {
|
|||||||
return REMOTE_CONTACT_LINK;
|
return REMOTE_CONTACT_LINK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO replace with real implementation
|
@Override
|
||||||
@SuppressWarnings("SameParameterValue")
|
public PendingContact addPendingContact(String link, String alias)
|
||||||
private static String getRandomBase32String(int length) {
|
throws DbException, FormatException {
|
||||||
Random random = new Random();
|
PendingContact p =
|
||||||
char[] c = new char[length];
|
pendingContactFactory.createPendingContact(link, alias);
|
||||||
for (int i = 0; i < length; i++) {
|
db.transaction(false, txn -> db.addPendingContact(txn, p));
|
||||||
int character = random.nextInt(32);
|
return p;
|
||||||
if (character < 26) c[i] = (char) ('a' + character);
|
|
||||||
else c[i] = (char) ('2' + (character - 26));
|
|
||||||
}
|
|
||||||
return new String(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addPendingContact(String link, String alias)
|
public Collection<PendingContact> getPendingContacts() throws DbException {
|
||||||
throws DbException {
|
return db.transactionWithResult(true, db::getPendingContacts);
|
||||||
// TODO replace with real implementation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<PendingContact> getPendingContacts() {
|
public void removePendingContact(PendingContactId p) throws DbException {
|
||||||
// TODO replace with real implementation
|
db.transaction(false, txn -> db.removePendingContact(txn, p));
|
||||||
return emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removePendingContact(PendingContactId id) throws DbException {
|
|
||||||
// TODO replace with real implementation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -28,4 +28,10 @@ public class ContactModule {
|
|||||||
ContactExchangeTaskImpl contactExchangeTask) {
|
ContactExchangeTaskImpl contactExchangeTask) {
|
||||||
return contactExchangeTask;
|
return contactExchangeTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
PendingContactFactory providePendingContactFactory(
|
||||||
|
PendingContactFactoryImpl pendingContactFactory) {
|
||||||
|
return pendingContactFactory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
|
|
||||||
|
interface PendingContactFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link PendingContact} from the given handshake link and alias.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedVersionException If the link uses a format version
|
||||||
|
* that is not supported
|
||||||
|
* @throws FormatException If the link is invalid
|
||||||
|
*/
|
||||||
|
PendingContact createPendingContact(String link, String alias)
|
||||||
|
throws FormatException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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.contact.PendingContactId;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.util.Base32;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.lang.System.arraycopy;
|
||||||
|
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.LINK_REGEX;
|
||||||
|
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
|
||||||
|
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||||
|
|
||||||
|
class PendingContactFactoryImpl implements PendingContactFactory {
|
||||||
|
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PendingContactFactoryImpl(CryptoComponent crypto, Clock clock) {
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PendingContact createPendingContact(String link, String alias)
|
||||||
|
throws FormatException {
|
||||||
|
PublicKey publicKey = parseHandshakeLink(link);
|
||||||
|
PendingContactId id = getPendingContactId(publicKey);
|
||||||
|
long timestamp = clock.currentTimeMillis();
|
||||||
|
return new PendingContact(id, publicKey.getEncoded(), alias,
|
||||||
|
WAITING_FOR_CONNECTION, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
if (version != FORMAT_VERSION)
|
||||||
|
throw new UnsupportedVersionException(version < FORMAT_VERSION);
|
||||||
|
byte[] publicKeyBytes = new byte[base32.length - 1];
|
||||||
|
arraycopy(base32, 1, publicKeyBytes, 0, publicKeyBytes.length);
|
||||||
|
try {
|
||||||
|
KeyParser parser = crypto.getAgreementKeyParser();
|
||||||
|
return parser.parsePublicKey(publicKeyBytes);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PendingContactId getPendingContactId(PublicKey publicKey) {
|
||||||
|
byte[] hash = crypto.hash(ID_LABEL, publicKey.getEncoded());
|
||||||
|
return new PendingContactId(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.BdfReader;
|
import org.briarproject.bramble.api.data.BdfReader;
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||||
import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
|
import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
|
||||||
import org.briarproject.bramble.api.keyagreement.UnsupportedVersionException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||||
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final KeyManager keyManager = context.mock(KeyManager.class);
|
private final KeyManager keyManager = context.mock(KeyManager.class);
|
||||||
private final IdentityManager identityManager =
|
private final IdentityManager identityManager =
|
||||||
context.mock(IdentityManager.class);
|
context.mock(IdentityManager.class);
|
||||||
|
private final PendingContactFactory pendingContactFactory =
|
||||||
|
context.mock(PendingContactFactory.class);
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
private final Author remote = getAuthor();
|
private final Author remote = getAuthor();
|
||||||
private final LocalAuthor localAuthor = getLocalAuthor();
|
private final LocalAuthor localAuthor = getLocalAuthor();
|
||||||
@@ -56,8 +58,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final ContactId contactId = contact.getId();
|
private final ContactId contactId = contact.getId();
|
||||||
|
|
||||||
public ContactManagerImplTest() {
|
public ContactManagerImplTest() {
|
||||||
contactManager =
|
contactManager = new ContactManagerImpl(db, keyManager,
|
||||||
new ContactManagerImpl(db, keyManager, identityManager);
|
identityManager, pendingContactFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
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.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.util.Base32;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import static java.lang.System.arraycopy;
|
||||||
|
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
|
||||||
|
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.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
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;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class PendingContactFactoryImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
private final KeyParser keyParser = context.mock(KeyParser.class);
|
||||||
|
private final PublicKey publicKey = context.mock(PublicKey.class);
|
||||||
|
|
||||||
|
private final PendingContactFactory pendingContactFactory =
|
||||||
|
new PendingContactFactoryImpl(crypto, clock);
|
||||||
|
private final String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||||
|
private final byte[] publicKeyBytes = getRandomBytes(RAW_LINK_BYTES - 1);
|
||||||
|
private final byte[] idBytes = getRandomId();
|
||||||
|
private final long timestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsSyntacticallyInvalidLink() throws Exception {
|
||||||
|
pendingContactFactory.createPendingContact("briar://potato", alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRejectsLinkWithUnknownFormatVersion() throws Exception {
|
||||||
|
String link = encodeLink(FORMAT_VERSION + 1);
|
||||||
|
try {
|
||||||
|
pendingContactFactory.createPendingContact(link, alias);
|
||||||
|
fail();
|
||||||
|
} catch (UnsupportedVersionException e) {
|
||||||
|
assertFalse(e.isTooOld());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsLinkWithInvalidPublicKey() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(crypto).getAgreementKeyParser();
|
||||||
|
will(returnValue(keyParser));
|
||||||
|
oneOf(keyParser).parsePublicKey(with(equal(publicKeyBytes)));
|
||||||
|
will(throwException(new GeneralSecurityException()));
|
||||||
|
}});
|
||||||
|
|
||||||
|
pendingContactFactory.createPendingContact(encodeLink(), alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptsValidLinkWithoutPrefix() throws Exception {
|
||||||
|
testAcceptsValidLink(encodeLink());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptsValidLinkWithPrefix() throws Exception {
|
||||||
|
testAcceptsValidLink("briar://" + encodeLink());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptsValidLinkWithRubbish() throws Exception {
|
||||||
|
testAcceptsValidLink("before " + encodeLink() + " after");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptsValidLinkWithPrefixAndRubbish() throws Exception {
|
||||||
|
testAcceptsValidLink("before briar://" + encodeLink() + " after");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testAcceptsValidLink(String link) throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(crypto).getAgreementKeyParser();
|
||||||
|
will(returnValue(keyParser));
|
||||||
|
oneOf(keyParser).parsePublicKey(with(equal(publicKeyBytes)));
|
||||||
|
will(returnValue(publicKey));
|
||||||
|
allowing(publicKey).getEncoded();
|
||||||
|
will(returnValue(publicKeyBytes));
|
||||||
|
oneOf(crypto).hash(ID_LABEL, publicKeyBytes);
|
||||||
|
will(returnValue(idBytes));
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(timestamp));
|
||||||
|
}});
|
||||||
|
|
||||||
|
PendingContact p =
|
||||||
|
pendingContactFactory.createPendingContact(link, alias);
|
||||||
|
assertArrayEquals(idBytes, p.getId().getBytes());
|
||||||
|
assertArrayEquals(publicKeyBytes, p.getPublicKey());
|
||||||
|
assertEquals(alias, p.getAlias());
|
||||||
|
assertEquals(WAITING_FOR_CONNECTION, p.getState());
|
||||||
|
assertEquals(timestamp, p.getTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encodeLink() {
|
||||||
|
return encodeLink(FORMAT_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encodeLink(int formatVersion) {
|
||||||
|
byte[] rawLink = new byte[RAW_LINK_BYTES];
|
||||||
|
rawLink[0] = (byte) formatVersion;
|
||||||
|
arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length);
|
||||||
|
String base32 = Base32.encode(rawLink).toLowerCase();
|
||||||
|
assertEquals(BASE32_LINK_BYTES, base32.length());
|
||||||
|
return base32;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,11 @@ package org.briarproject.bramble.keyagreement;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.Bytes;
|
import org.briarproject.bramble.api.Bytes;
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.BdfReader;
|
import org.briarproject.bramble.api.data.BdfReader;
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||||
import org.briarproject.bramble.api.keyagreement.UnsupportedVersionException;
|
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package org.briarproject.bramble.util;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class Base32Test extends BrambleTestCase {
|
||||||
|
|
||||||
|
// Test vectors from RFC 4648
|
||||||
|
// https://tools.ietf.org/html/rfc4648#section-10
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncoding() {
|
||||||
|
assertEquals("", Base32.encode(new byte[0]));
|
||||||
|
assertEquals("MY", Base32.encode(new byte[] {'f'}));
|
||||||
|
assertEquals("MZXQ", Base32.encode(new byte[] {'f', 'o'}));
|
||||||
|
assertEquals("MZXW6", Base32.encode(new byte[] {'f', 'o', 'o'}));
|
||||||
|
assertEquals("MZXW6YQ", Base32.encode(new byte[] {'f', 'o', 'o', 'b'}));
|
||||||
|
assertEquals("MZXW6YTB",
|
||||||
|
Base32.encode(new byte[] {'f', 'o', 'o', 'b', 'a'}));
|
||||||
|
assertEquals("MZXW6YTBOI",
|
||||||
|
Base32.encode(new byte[] {'f', 'o', 'o', 'b', 'a', 'r'}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStrictDecoding() {
|
||||||
|
testDecoding(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonStrictDecoding() {
|
||||||
|
testDecoding(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testDecoding(boolean strict) {
|
||||||
|
assertArrayEquals(new byte[0], Base32.decode("", strict));
|
||||||
|
assertArrayEquals(new byte[] {'f'}, Base32.decode("MY", strict));
|
||||||
|
assertArrayEquals(new byte[] {'f', 'o'}, Base32.decode("MZXQ", strict));
|
||||||
|
assertArrayEquals(new byte[] {'f', 'o', 'o'},
|
||||||
|
Base32.decode("MZXW6", strict));
|
||||||
|
assertArrayEquals(new byte[] {'f', 'o', 'o', 'b'},
|
||||||
|
Base32.decode("MZXW6YQ", strict));
|
||||||
|
assertArrayEquals(new byte[] {'f', 'o', 'o', 'b', 'a'},
|
||||||
|
Base32.decode("MZXW6YTB", strict));
|
||||||
|
assertArrayEquals(new byte[] {'f', 'o', 'o', 'b', 'a', 'r'},
|
||||||
|
Base32.decode("MZXW6YTBOI", strict));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testStrictDecodingRejectsNonZeroUnusedBits() {
|
||||||
|
Base32.decode("MZ", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonStrictDecodingAcceptsNonZeroUnusedBits() {
|
||||||
|
assertArrayEquals(new byte[] {'f'}, Base32.decode("MZ", false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRoundTrip() {
|
||||||
|
Random random = new Random();
|
||||||
|
byte[] data = new byte[100 + random.nextInt(100)];
|
||||||
|
random.nextBytes(data);
|
||||||
|
assertArrayEquals(data, Base32.decode(Base32.encode(data), true));
|
||||||
|
assertArrayEquals(data, Base32.decode(Base32.encode(data), false));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.contact.ContactManager.LINK_REGEX;
|
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -105,7 +105,7 @@ public class AddContactViewModel extends AndroidViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Boolean> getAddContactResult() {
|
LiveData<Boolean> getAddContactResult() {
|
||||||
return addContactResult;
|
return addContactResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import javax.inject.Inject;
|
|||||||
import static android.content.Context.CLIPBOARD_SERVICE;
|
import static android.content.Context.CLIPBOARD_SERVICE;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.bramble.api.contact.ContactManager.LINK_REGEX;
|
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import com.google.zxing.Result;
|
import com.google.zxing.Result;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
|
||||||
@@ -22,7 +23,6 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
|||||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||||
import org.briarproject.bramble.api.keyagreement.UnsupportedVersionException;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementAbortedEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementAbortedEvent;
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
||||||
|
|||||||
Reference in New Issue
Block a user