diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java index 2f5170b91..3e0e02054 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java @@ -1,18 +1,26 @@ package org.briarproject.bramble.api.keyagreement; -public interface KeyAgreementConstants { +import org.briarproject.bramble.api.mailbox.MailboxConstants; - /** - * The version of the BQP protocol used in beta releases. This version - * number is reserved. - */ - byte BETA_PROTOCOL_VERSION = 89; +public interface KeyAgreementConstants { /** * The current version of the BQP protocol. */ byte PROTOCOL_VERSION = 4; + /** + * The QR code format identifier, used to distinguish BQP QR codes from + * QR codes used for other purposes. See + * {@link MailboxConstants#QR_FORMAT_ID}. + */ + byte QR_FORMAT_ID = 0; + + /** + * The QR code format version. + */ + byte QR_FORMAT_VERSION = PROTOCOL_VERSION; + /** * The length of the BQP key commitment in bytes. */ diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/PayloadParser.java b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/PayloadParser.java index c6458443a..b73ce7b12 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/PayloadParser.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/PayloadParser.java @@ -7,5 +7,5 @@ import java.io.IOException; @NotNullByDefault public interface PayloadParser { - Payload parse(byte[] raw) throws IOException; + Payload parse(String payload) throws IOException; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java index 02c7b8a5e..16896fc4b 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxConstants.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.api.mailbox; +import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants; import org.briarproject.bramble.api.plugin.TransportId; import java.util.List; @@ -19,6 +20,18 @@ public interface MailboxConstants { */ TransportId ID = new TransportId("org.briarproject.bramble.mailbox"); + /** + * The QR code format identifier, used to distinguish mailbox QR codes + * from QR codes used for other purposes. See + * {@link KeyAgreementConstants#QR_FORMAT_ID}; + */ + byte QR_FORMAT_ID = 1; + + /** + * The QR code format version. + */ + byte QR_FORMAT_VERSION = 0; + /** * Mailbox API versions that we support as a client. This is reported to our * contacts by {@link MailboxUpdateManager}. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPairingState.java b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPairingState.java index 47c055aad..4b74013de 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPairingState.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/mailbox/MailboxPairingState.java @@ -1,5 +1,7 @@ package org.briarproject.bramble.api.mailbox; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; + public abstract class MailboxPairingState { public abstract static class Pending extends MailboxPairingState { @@ -29,6 +31,14 @@ public abstract class MailboxPairingState { } public static class InvalidQrCode extends MailboxPairingState { + + public final QrCodeType qrCodeType; + public final int formatVersion; + + public InvalidQrCode(QrCodeType qrCodeType, int formatVersion) { + this.qrCodeType = qrCodeType; + this.formatVersion = formatVersion; + } } public static class MailboxAlreadyPaired extends MailboxPairingState { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/QrCodeClassifier.java b/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/QrCodeClassifier.java new file mode 100644 index 000000000..00600acf0 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/qrcode/QrCodeClassifier.java @@ -0,0 +1,16 @@ +package org.briarproject.bramble.api.qrcode; + +import org.briarproject.bramble.api.Pair; +import org.briarproject.nullsafety.NotNullByDefault; + +@NotNullByDefault +public interface QrCodeClassifier { + + enum QrCodeType { + BQP, + MAILBOX, + UNKNOWN + } + + Pair classifyQrCode(String payload); +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java b/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java index 2797aaf72..d9a19e70a 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/util/StringUtils.java @@ -3,7 +3,6 @@ package org.briarproject.bramble.util; import org.briarproject.bramble.api.FormatException; import org.briarproject.nullsafety.NotNullByDefault; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; @@ -17,13 +16,18 @@ import javax.annotation.Nullable; import static java.nio.charset.CodingErrorAction.IGNORE; import static java.util.regex.Pattern.CASE_INSENSITIVE; +@SuppressWarnings("CharsetObjectCanBeUsed") @NotNullByDefault public class StringUtils { - private static final Charset UTF_8 = Charset.forName("UTF-8"); - private static Pattern MAC = Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" + - "[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}", - CASE_INSENSITIVE); + public static final Charset UTF_8 = Charset.forName("UTF-8"); + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + private static final Pattern MAC = + Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" + + "[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}", + CASE_INSENSITIVE); private static final char[] HEX = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', @@ -45,11 +49,7 @@ public class StringUtils { } public static byte[] toUtf8(String s) { - try { - return s.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } + return s.getBytes(UTF_8); } public static String fromUtf8(byte[] bytes) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java index 85ec0372a..b84ae1720 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java @@ -17,6 +17,7 @@ import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.mailbox.MailboxModule; import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.properties.PropertiesModule; +import org.briarproject.bramble.qrcode.QrCodeModule; import org.briarproject.bramble.record.RecordModule; import org.briarproject.bramble.reliability.ReliabilityModule; import org.briarproject.bramble.rendezvous.RendezvousModule; @@ -47,6 +48,7 @@ import dagger.Module; MailboxModule.class, PluginModule.class, PropertiesModule.class, + QrCodeModule.class, RecordModule.class, ReliabilityModule.class, RendezvousModule.class, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java index 879c4505b..f0f8fe737 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java @@ -19,7 +19,6 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.nio.charset.Charset; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -29,6 +28,7 @@ import javax.inject.Inject; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.StringUtils.UTF_8; import static org.briarproject.bramble.util.StringUtils.fromHexString; import static org.briarproject.bramble.util.StringUtils.toHexString; @@ -99,7 +99,7 @@ class AccountManagerImpl implements AccountManager { } try { BufferedReader reader = new BufferedReader(new InputStreamReader( - new FileInputStream(f), Charset.forName("UTF-8"))); + new FileInputStream(f), UTF_8)); String key = reader.readLine(); reader.close(); return key; @@ -151,7 +151,7 @@ class AccountManagerImpl implements AccountManager { @GuardedBy("stateChangeLock") private void writeDbKeyToFile(String key, File f) throws IOException { FileOutputStream out = new FileOutputStream(f); - out.write(key.getBytes(Charset.forName("UTF-8"))); + out.write(key.getBytes(UTF_8)); out.flush(); out.close(); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java index c81ef00d0..06ce15f29 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java @@ -29,7 +29,6 @@ import org.briarproject.nullsafety.NotNullByDefault; import org.whispersystems.curve25519.Curve25519; import org.whispersystems.curve25519.Curve25519KeyPair; -import java.nio.charset.Charset; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import java.security.Provider; @@ -51,6 +50,7 @@ import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHE import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES; import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.now; +import static org.briarproject.bramble.util.StringUtils.US_ASCII; @NotNullByDefault class CryptoComponentImpl implements CryptoComponent { @@ -460,7 +460,7 @@ class CryptoComponentImpl implements CryptoComponent { @Override public String encodeOnion(byte[] publicKey) { Digest digest = new SHA3Digest(256); - byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII")); + byte[] label = ".onion checksum".getBytes(US_ASCII); digest.update(label, 0, label.length); digest.update(publicKey, 0, publicKey.length); digest.update(ONION_HS_PROTOCOL_VERSION); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/MessageEncrypter.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/MessageEncrypter.java index 3e00e2d1b..9a97b2f7a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/MessageEncrypter.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/MessageEncrypter.java @@ -39,12 +39,13 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; -import java.nio.charset.Charset; import java.security.SecureRandom; import java.util.Scanner; import javax.annotation.concurrent.Immutable; +import static org.briarproject.bramble.util.StringUtils.UTF_8; + @Immutable @NotNullByDefault public class MessageEncrypter { @@ -228,7 +229,7 @@ public class MessageEncrypter { PublicKey publicKey = encrypter.getKeyParser().parsePublicKey(keyBytes); String message = readFully(System.in); - byte[] plaintext = message.getBytes(Charset.forName("UTF-8")); + byte[] plaintext = message.getBytes(UTF_8); byte[] ciphertext = encrypter.encrypt(publicKey, plaintext); System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH)); } @@ -242,7 +243,7 @@ public class MessageEncrypter { encrypter.getKeyParser().parsePrivateKey(keyBytes); byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in)); byte[] plaintext = encrypter.decrypt(privateKey, ciphertext); - System.out.println(new String(plaintext, Charset.forName("UTF-8"))); + System.out.println(new String(plaintext, UTF_8)); } private static String readFully(InputStream in) throws IOException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementModule.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementModule.java index 819832820..54d28d675 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementModule.java @@ -1,7 +1,5 @@ package org.briarproject.bramble.keyagreement; -import org.briarproject.bramble.api.data.BdfReaderFactory; -import org.briarproject.bramble.api.data.BdfWriterFactory; import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; import org.briarproject.bramble.api.keyagreement.PayloadEncoder; import org.briarproject.bramble.api.keyagreement.PayloadParser; @@ -19,13 +17,13 @@ public class KeyAgreementModule { } @Provides - PayloadEncoder providePayloadEncoder(BdfWriterFactory bdfWriterFactory) { - return new PayloadEncoderImpl(bdfWriterFactory); + PayloadEncoder providePayloadEncoder(PayloadEncoderImpl payloadEncoder) { + return payloadEncoder; } @Provides - PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) { - return new PayloadParserImpl(bdfReaderFactory); + PayloadParser providePayloadParser(PayloadParserImpl payloadParser) { + return payloadParser; } @Provides diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadEncoderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadEncoderImpl.java index b0ee035e5..ca90f5f96 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadEncoderImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadEncoderImpl.java @@ -13,7 +13,8 @@ import java.io.IOException; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; +import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_ID; +import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION; @Immutable @NotNullByDefault @@ -29,7 +30,8 @@ class PayloadEncoderImpl implements PayloadEncoder { @Override public byte[] encode(Payload p) { ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(PROTOCOL_VERSION); + int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION; + out.write(formatIdAndVersion); BdfWriter w = bdfWriterFactory.createWriter(out); try { w.writeListStart(); // Payload start diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java index 585516c64..f4d81665d 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/PayloadParserImpl.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.keyagreement; import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfReader; @@ -11,6 +12,8 @@ import org.briarproject.bramble.api.keyagreement.TransportDescriptor; import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; import org.briarproject.nullsafety.NotNullByDefault; import java.io.ByteArrayInputStream; @@ -21,34 +24,41 @@ import java.util.List; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.BETA_PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; -import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; +import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP; +import static org.briarproject.bramble.util.StringUtils.ISO_8859_1; @Immutable @NotNullByDefault class PayloadParserImpl implements PayloadParser { private final BdfReaderFactory bdfReaderFactory; + private final QrCodeClassifier qrCodeClassifier; @Inject - PayloadParserImpl(BdfReaderFactory bdfReaderFactory) { + PayloadParserImpl(BdfReaderFactory bdfReaderFactory, + QrCodeClassifier qrCodeClassifier) { this.bdfReaderFactory = bdfReaderFactory; + this.qrCodeClassifier = qrCodeClassifier; } @Override - public Payload parse(byte[] raw) throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream(raw); - // First byte: the protocol version - int protocolVersion = in.read(); - if (protocolVersion == -1) throw new FormatException(); - if (protocolVersion != PROTOCOL_VERSION) { - boolean tooOld = protocolVersion < PROTOCOL_VERSION || - protocolVersion == BETA_PROTOCOL_VERSION; + public Payload parse(String payloadString) throws IOException { + Pair typeAndVersion = + qrCodeClassifier.classifyQrCode(payloadString); + if (typeAndVersion.getFirst() != BQP) throw new FormatException(); + int formatVersion = typeAndVersion.getSecond(); + if (formatVersion != QR_FORMAT_VERSION) { + boolean tooOld = formatVersion < QR_FORMAT_VERSION; throw new UnsupportedVersionException(tooOld); } + byte[] raw = payloadString.getBytes(ISO_8859_1); + ByteArrayInputStream in = new ByteArrayInputStream(raw); + // First byte: the format identifier and version (already parsed) + if (in.read() == -1) throw new AssertionError(); // The rest of the payload is a BDF list with one or more elements BdfReader r = bdfReaderFactory.createReader(in); BdfList payload = r.readList(); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskFactoryImpl.java index d90f05d90..a8002e27d 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskFactoryImpl.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier; import org.briarproject.bramble.api.system.Clock; import org.briarproject.nullsafety.NotNullByDefault; @@ -25,6 +26,7 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory { private final MailboxApi api; private final MailboxSettingsManager mailboxSettingsManager; private final MailboxUpdateManager mailboxUpdateManager; + private final QrCodeClassifier qrCodeClassifier; @Inject MailboxPairingTaskFactoryImpl( @@ -34,7 +36,8 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory { Clock clock, MailboxApi api, MailboxSettingsManager mailboxSettingsManager, - MailboxUpdateManager mailboxUpdateManager) { + MailboxUpdateManager mailboxUpdateManager, + QrCodeClassifier qrCodeClassifier) { this.eventExecutor = eventExecutor; this.db = db; this.crypto = crypto; @@ -42,12 +45,13 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory { this.api = api; this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxUpdateManager = mailboxUpdateManager; + this.qrCodeClassifier = qrCodeClassifier; } @Override public MailboxPairingTask createPairingTask(String qrCodePayload) { return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db, crypto, clock, api, mailboxSettingsManager, - mailboxUpdateManager); + mailboxUpdateManager, qrCodeClassifier); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java index 8ce25be9d..98cdf65b0 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImpl.java @@ -2,6 +2,7 @@ package org.briarproject.bramble.mailbox; import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.db.DatabaseComponent; @@ -14,13 +15,14 @@ import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException; import org.briarproject.nullsafety.NotNullByDefault; import java.io.IOException; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -32,7 +34,10 @@ import javax.annotation.concurrent.ThreadSafe; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.StringUtils.ISO_8859_1; @ThreadSafe @NotNullByDefault @@ -40,9 +45,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { private final static Logger LOG = getLogger(MailboxPairingTaskImpl.class.getName()); - @SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19 - private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); - private static final int VERSION_REQUIRED = 32; private final String payload; private final Executor eventExecutor; @@ -52,6 +54,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { private final MailboxApi api; private final MailboxSettingsManager mailboxSettingsManager; private final MailboxUpdateManager mailboxUpdateManager; + private final QrCodeClassifier qrCodeClassifier; private final long timeStarted; private final Object lock = new Object(); @@ -69,7 +72,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { Clock clock, MailboxApi api, MailboxSettingsManager mailboxSettingsManager, - MailboxUpdateManager mailboxUpdateManager) { + MailboxUpdateManager mailboxUpdateManager, + QrCodeClassifier qrCodeClassifier) { this.payload = payload; this.eventExecutor = eventExecutor; this.db = db; @@ -78,6 +82,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { this.api = api; this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxUpdateManager = mailboxUpdateManager; + this.qrCodeClassifier = qrCodeClassifier; timeStarted = clock.currentTimeMillis(); state = new MailboxPairingState.QrCodeReceived(timeStarted); } @@ -101,10 +106,20 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { @Override public void run() { + Pair typeAndVersion = + qrCodeClassifier.classifyQrCode(payload); + QrCodeType qrCodeType = typeAndVersion.getFirst(); + int formatVersion = typeAndVersion.getSecond(); + if (qrCodeType != MAILBOX || formatVersion != QR_FORMAT_VERSION) { + setState(new MailboxPairingState.InvalidQrCode(qrCodeType, + formatVersion)); + return; + } try { pairMailbox(); } catch (FormatException e) { - onMailboxError(e, new MailboxPairingState.InvalidQrCode()); + onMailboxError(e, new MailboxPairingState.InvalidQrCode(qrCodeType, + formatVersion)); } catch (MailboxAlreadyPairedException e) { onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired()); } catch (IOException e) { @@ -169,14 +184,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask { } throw new FormatException(); } - int version = bytes[0] & 0xFF; - if (version != VERSION_REQUIRED) { - if (LOG.isLoggable(WARNING)) { - LOG.warning("QR code has not version " + VERSION_REQUIRED + - ": " + version); - } - throw new FormatException(); - } LOG.info("QR code is valid"); byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33); String onion = crypto.encodeOnion(onionPubKey); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index c7fa95887..128a3a516 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -46,7 +46,6 @@ import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -100,6 +99,7 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion; +import static org.briarproject.bramble.util.StringUtils.UTF_8; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @MethodsNotNullByDefault @@ -419,9 +419,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path); String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath(); append(strb, "ClientTransportPlugin snowflake exec", snowflakePath); - //noinspection CharsetObjectCanBeUsed - return new ByteArrayInputStream( - strb.toString().getBytes(Charset.forName("UTF-8"))); + return new ByteArrayInputStream(strb.toString().getBytes(UTF_8)); } private void listFiles(File f) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java index d2b75322a..ee91fa9b4 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java @@ -7,7 +7,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; import org.bouncycastle.util.encoders.Base64; import org.briarproject.bramble.api.crypto.CryptoComponent; -import java.nio.charset.Charset; +import static org.briarproject.bramble.util.StringUtils.US_ASCII; class TorRendezvousCryptoImpl implements TorRendezvousCrypto { @@ -31,6 +31,6 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto { EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC); byte[] hash = spec.getH(); byte[] base64 = Base64.encode(hash); - return "ED25519-V3:" + new String(base64, Charset.forName("US-ASCII")); + return "ED25519-V3:" + new String(base64, US_ASCII); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeClassifierImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeClassifierImpl.java new file mode 100644 index 000000000..5dcd5f3ce --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeClassifierImpl.java @@ -0,0 +1,42 @@ +package org.briarproject.bramble.qrcode; + +import org.briarproject.bramble.api.Pair; +import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants; +import org.briarproject.bramble.api.mailbox.MailboxConstants; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier; +import org.briarproject.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.UNKNOWN; +import static org.briarproject.bramble.util.StringUtils.ISO_8859_1; + +@Immutable +@NotNullByDefault +class QrCodeClassifierImpl implements QrCodeClassifier { + + @Inject + QrCodeClassifierImpl() { + } + + @Override + public Pair classifyQrCode(String payload) { + byte[] bytes = payload.getBytes(ISO_8859_1); + if (bytes.length == 0) return new Pair<>(UNKNOWN, 0); + // If this is a Bramble QR code then the first byte encodes the + // format ID (3 bits) and version (5 bits) + int formatIdAndVersion = bytes[0] & 0xFF; + int formatId = formatIdAndVersion >> 5; + int formatVersion = formatIdAndVersion & 0x1F; + if (formatId == KeyAgreementConstants.QR_FORMAT_ID) { + return new Pair<>(BQP, formatVersion); + } + if (formatId == MailboxConstants.QR_FORMAT_ID) { + return new Pair<>(MAILBOX, formatVersion); + } + return new Pair<>(UNKNOWN, 0); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeModule.java b/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeModule.java new file mode 100644 index 000000000..59fe09190 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/qrcode/QrCodeModule.java @@ -0,0 +1,16 @@ +package org.briarproject.bramble.qrcode; + +import org.briarproject.bramble.api.qrcode.QrCodeClassifier; + +import dagger.Module; +import dagger.Provides; + +@Module +public class QrCodeModule { + + @Provides + QrCodeClassifier provideQrCodeClassifier( + QrCodeClassifierImpl qrCodeClassifier) { + return qrCodeClassifier; + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java index e5c345e53..c013158ce 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java @@ -20,7 +20,6 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.nio.charset.Charset; import javax.annotation.Nullable; @@ -34,6 +33,7 @@ import static org.briarproject.bramble.test.TestUtils.getIdentity; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getTestDirectory; +import static org.briarproject.bramble.util.StringUtils.UTF_8; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.toHexString; import static org.junit.Assert.assertArrayEquals; @@ -342,7 +342,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase { private void storeDatabaseKey(File f, String hex) throws IOException { f.getParentFile().mkdirs(); FileOutputStream out = new FileOutputStream(f); - out.write(hex.getBytes(Charset.forName("UTF-8"))); + out.write(hex.getBytes(UTF_8)); out.flush(); out.close(); } @@ -350,7 +350,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase { @Nullable private String loadDatabaseKey(File f) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader( - new FileInputStream(f), Charset.forName("UTF-8"))); + new FileInputStream(f), UTF_8)); String hex = reader.readLine(); reader.close(); return hex; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java index f9d74e081..02f1cbdeb 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/keyagreement/PayloadParserImplTest.java @@ -2,21 +2,26 @@ package org.briarproject.bramble.keyagreement; import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.keyagreement.Payload; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; import org.briarproject.bramble.test.BrambleMockTestCase; import org.jmock.Expectations; import org.junit.Test; import java.io.ByteArrayInputStream; -import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.BETA_PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; -import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; +import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -26,32 +31,29 @@ public class PayloadParserImplTest extends BrambleMockTestCase { private final BdfReaderFactory bdfReaderFactory = context.mock(BdfReaderFactory.class); + private final QrCodeClassifier qrCodeClassifier = + context.mock(QrCodeClassifier.class); private final BdfReader bdfReader = context.mock(BdfReader.class); + private final String payload = getRandomString(123); + private final PayloadParserImpl payloadParser = - new PayloadParserImpl(bdfReaderFactory); + new PayloadParserImpl(bdfReaderFactory, qrCodeClassifier); @Test(expected = FormatException.class) - public void testThrowsFormatExceptionIfPayloadIsEmpty() throws Exception { - payloadParser.parse(new byte[0]); + public void testThrowsFormatExceptionForWrongQrCodeType() throws Exception { + expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION); + + payloadParser.parse(payload); } @Test public void testThrowsUnsupportedVersionExceptionForOldVersion() throws Exception { - try { - payloadParser.parse(new byte[] {PROTOCOL_VERSION - 1}); - fail(); - } catch (UnsupportedVersionException e) { - assertTrue(e.isTooOld()); - } - } + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION - 1); - @Test - public void testThrowsUnsupportedVersionExceptionForBetaVersion() - throws Exception { try { - payloadParser.parse(new byte[] {BETA_PROTOCOL_VERSION}); + payloadParser.parse(payload); fail(); } catch (UnsupportedVersionException e) { assertTrue(e.isTooOld()); @@ -61,8 +63,10 @@ public class PayloadParserImplTest extends BrambleMockTestCase { @Test public void testThrowsUnsupportedVersionExceptionForNewVersion() throws Exception { + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION + 1); + try { - payloadParser.parse(new byte[] {PROTOCOL_VERSION + 1}); + payloadParser.parse(payload); fail(); } catch (UnsupportedVersionException e) { assertFalse(e.isTooOld()); @@ -71,6 +75,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase { @Test(expected = FormatException.class) public void testThrowsFormatExceptionForEmptyList() throws Exception { + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); + context.checking(new Expectations() {{ oneOf(bdfReaderFactory).createReader( with(any(ByteArrayInputStream.class))); @@ -79,7 +85,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase { will(returnValue(new BdfList())); }}); - payloadParser.parse(new byte[] {PROTOCOL_VERSION}); + payloadParser.parse(payload); } @Test(expected = FormatException.class) @@ -87,6 +93,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase { throws Exception { byte[] commitment = getRandomBytes(COMMIT_LENGTH); + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); + context.checking(new Expectations() {{ oneOf(bdfReaderFactory).createReader( with(any(ByteArrayInputStream.class))); @@ -97,7 +105,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase { will(returnValue(false)); }}); - payloadParser.parse(new byte[] {PROTOCOL_VERSION}); + payloadParser.parse(payload); } @Test(expected = FormatException.class) @@ -105,6 +113,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase { throws Exception { byte[] commitment = getRandomBytes(COMMIT_LENGTH - 1); + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(bdfReaderFactory).createReader( with(any(ByteArrayInputStream.class))); @@ -115,7 +124,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase { will(returnValue(true)); }}); - payloadParser.parse(new byte[] {PROTOCOL_VERSION}); + payloadParser.parse(payload); } @Test(expected = FormatException.class) @@ -123,6 +132,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase { throws Exception { byte[] commitment = getRandomBytes(COMMIT_LENGTH + 1); + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(bdfReaderFactory).createReader( with(any(ByteArrayInputStream.class))); @@ -133,12 +143,14 @@ public class PayloadParserImplTest extends BrambleMockTestCase { will(returnValue(true)); }}); - payloadParser.parse(new byte[] {PROTOCOL_VERSION}); + payloadParser.parse(payload); } @Test public void testAcceptsPayloadWithNoDescriptors() throws Exception { byte[] commitment = getRandomBytes(COMMIT_LENGTH); + + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(bdfReaderFactory).createReader( with(any(ByteArrayInputStream.class))); @@ -149,8 +161,16 @@ public class PayloadParserImplTest extends BrambleMockTestCase { will(returnValue(true)); }}); - Payload p = payloadParser.parse(new byte[] {PROTOCOL_VERSION}); + Payload p = payloadParser.parse(payload); assertArrayEquals(commitment, p.getCommitment()); assertTrue(p.getTransportDescriptors().isEmpty()); } + + private void expectClassifyQrCode(String payload, QrCodeType qrCodeType, + int formatVersion) { + context.checking(new Expectations() {{ + oneOf(qrCodeClassifier).classifyQrCode(payload); + will(returnValue(new Pair<>(qrCodeType, formatVersion))); + }}); + } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java index 86763a525..91573e46d 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxPairingTaskImplTest.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.mailbox; +import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.db.DatabaseComponent; @@ -13,6 +14,8 @@ import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxVersion; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.DbExpectations; @@ -27,6 +30,9 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import static java.util.Collections.singletonList; +import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX; import static org.briarproject.bramble.mailbox.MailboxTestUtils.getQrCodePayload; import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; @@ -48,9 +54,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { context.mock(MailboxSettingsManager.class); private final MailboxUpdateManager mailboxUpdateManager = context.mock(MailboxUpdateManager.class); - private final MailboxPairingTaskFactory factory = - new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api, - mailboxSettingsManager, mailboxUpdateManager); + private final QrCodeClassifier qrCodeClassifier = + context.mock(QrCodeClassifier.class); private final String onion = getRandomString(56); private final byte[] onionBytes = getRandomBytes(32); @@ -75,23 +80,47 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { } @Test - public void testInvalidQrCode() { - MailboxPairingTask task1 = createPairingTask(getRandomString(42)); - task1.run(); - task1.addObserver(state -> + public void testInvalidQrCodeType() { + String payload = getRandomString(65); + MailboxPairingTask task = createPairingTask(payload); + + expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION); + + task.run(); + task.addObserver(state -> assertTrue(state instanceof MailboxPairingState.InvalidQrCode) ); + } - String goodLength = "00" + getRandomString(63); - MailboxPairingTask task2 = createPairingTask(goodLength); - task2.run(); - task2.addObserver(state -> + @Test + public void testInvalidQrCodeVersion() { + String payload = getRandomString(65); + MailboxPairingTask task = createPairingTask(payload); + + expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION + 1); + + task.run(); + task.addObserver(state -> + assertTrue(state instanceof MailboxPairingState.InvalidQrCode) + ); + } + + @Test + public void testInvalidQrCodeLength() { + String payload = getRandomString(42); + MailboxPairingTask task = createPairingTask(payload); + + expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION); + + task.run(); + task.addObserver(state -> assertTrue(state instanceof MailboxPairingState.InvalidQrCode) ); } @Test public void testSuccessfulPairing() throws Exception { + expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(crypto).encodeOnion(onionBytes); will(returnValue(onion)); @@ -156,6 +185,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { private void testApiException(Exception e, Class s) throws Exception { + expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(crypto).encodeOnion(onionBytes); will(returnValue(onion)); @@ -170,6 +200,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { @Test public void testDbException() throws Exception { + expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION); context.checking(new Expectations() {{ oneOf(crypto).encodeOnion(onionBytes); will(returnValue(onion)); @@ -206,6 +237,16 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase { will(returnValue(time)); }}); - return factory.createPairingTask(qrCodePayload); + return new MailboxPairingTaskImpl(qrCodePayload, executor, db, + crypto, clock, api, mailboxSettingsManager, + mailboxUpdateManager, qrCodeClassifier); + } + + private void expectClassifyQrCode(String payload, QrCodeType qrCodeType, + int formatVersion) { + context.checking(new Expectations() {{ + oneOf(qrCodeClassifier).classifyQrCode(payload); + will(returnValue(new Pair<>(qrCodeType, formatVersion))); + }}); } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxTestUtils.java b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxTestUtils.java index 4795fb739..5f90a6d5e 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxTestUtils.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/mailbox/MailboxTestUtils.java @@ -3,7 +3,6 @@ package org.briarproject.bramble.mailbox; import org.briarproject.bramble.api.WeakSingletonProvider; import java.nio.ByteBuffer; -import java.nio.charset.Charset; import javax.annotation.Nonnull; import javax.net.SocketFactory; @@ -11,20 +10,24 @@ import javax.net.SocketFactory; import okhttp3.OkHttpClient; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_ID; +import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION; import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.bramble.util.StringUtils.ISO_8859_1; class MailboxTestUtils { static String getQrCodePayload(byte[] onionBytes, byte[] setupToken) { + int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION; byte[] payloadBytes = ByteBuffer.allocate(65) - .put((byte) 32) // 1 + .put((byte) formatIdAndVersion) // 1 .put(onionBytes) // 32 .put(setupToken) // 32 .array(); - //noinspection CharsetObjectCanBeUsed - return new String(payloadBytes, Charset.forName("ISO-8859-1")); + return new String(payloadBytes, ISO_8859_1); } + // Used by mailbox integration tests static String getQrCodePayload(byte[] setupToken) { return getQrCodePayload(getRandomId(), setupToken); } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/qrcode/QrCodeClassifierImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/qrcode/QrCodeClassifierImplTest.java new file mode 100644 index 000000000..201d72b7e --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/qrcode/QrCodeClassifierImplTest.java @@ -0,0 +1,70 @@ +package org.briarproject.bramble.qrcode; + +import org.briarproject.bramble.api.Pair; +import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants; +import org.briarproject.bramble.api.mailbox.MailboxConstants; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier; +import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType; +import org.briarproject.bramble.test.BrambleTestCase; +import org.junit.Test; + +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX; +import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.UNKNOWN; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.util.StringUtils.ISO_8859_1; +import static org.junit.Assert.assertEquals; + +public class QrCodeClassifierImplTest extends BrambleTestCase { + + private final QrCodeClassifier classifier = new QrCodeClassifierImpl(); + + @Test + public void testClassifiesEmptyStringAsUnknown() { + Pair result = classifier.classifyQrCode(""); + assertEquals(UNKNOWN, result.getFirst()); + assertEquals(0, result.getSecond().intValue()); + } + + @Test + public void testClassifiesKeyAgreement() { + byte[] payloadBytes = getRandomBytes(123); + for (int version = 0; version < 32; version++) { + int typeAndVersion = + (KeyAgreementConstants.QR_FORMAT_ID << 5) | version; + payloadBytes[0] = (byte) typeAndVersion; + String payload = new String(payloadBytes, ISO_8859_1); + Pair result = + classifier.classifyQrCode(payload); + assertEquals(BQP, result.getFirst()); + assertEquals(version, result.getSecond().intValue()); + } + } + + @Test + public void testClassifiesMailbox() { + byte[] payloadBytes = getRandomBytes(123); + for (int version = 0; version < 32; version++) { + int typeAndVersion = + (MailboxConstants.QR_FORMAT_ID << 5) | version; + payloadBytes[0] = (byte) typeAndVersion; + String payload = new String(payloadBytes, ISO_8859_1); + Pair result = + classifier.classifyQrCode(payload); + assertEquals(MAILBOX, result.getFirst()); + assertEquals(version, result.getSecond().intValue()); + } + } + + @Test + public void testClassifiesUnknownFormatIdAsUnknown() { + byte[] payloadBytes = getRandomBytes(123); + int unknownFormatId = MailboxConstants.QR_FORMAT_ID + 1; + int typeAndVersion = unknownFormatId << 5; + payloadBytes[0] = (byte) typeAndVersion; + String payload = new String(payloadBytes, ISO_8859_1); + Pair result = classifier.classifyQrCode(payload); + assertEquals(UNKNOWN, result.getFirst()); + assertEquals(0, result.getSecond().intValue()); + } +} diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java index 2a145cc31..b8cbc828f 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/modem/ModemImpl.java @@ -12,7 +12,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.concurrent.Executor; import java.util.concurrent.Semaphore; @@ -33,6 +32,7 @@ import static java.util.logging.Level.WARNING; import static jssc.SerialPort.PURGE_RXCLEAR; import static jssc.SerialPort.PURGE_TXCLEAR; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.StringUtils.US_ASCII; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -41,7 +41,6 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener { private static final Logger LOG = Logger.getLogger(ModemImpl.class.getName()); - private static final Charset US_ASCII = Charset.forName("US-ASCII"); private static final int MAX_LINE_LENGTH = 256; private static final int[] BAUD_RATES = { 256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200 diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java index 18351c64c..375dfee2a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/nearby/AddNearbyContactViewModel.java @@ -60,7 +60,6 @@ import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.nullsafety.NotNullByDefault; import java.io.IOException; -import java.nio.charset.Charset; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -85,6 +84,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.StringUtils.ISO_8859_1; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; @@ -126,9 +126,6 @@ class AddNearbyContactViewModel extends AndroidViewModel REFUSED } - @SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19 - private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); - private final EventBus eventBus; private final AndroidExecutor androidExecutor; private final Executor ioExecutor; @@ -446,10 +443,7 @@ class AddNearbyContactViewModel extends AndroidViewModel // Ignore results until the KeyAgreementTask is ready if (!gotLocalPayload || gotRemotePayload || currentTask == null) return; try { - byte[] payloadBytes = result.getText().getBytes(ISO_8859_1); - if (LOG.isLoggable(INFO)) - LOG.info("Remote payload is " + payloadBytes.length + " bytes"); - Payload remotePayload = payloadParser.parse(payloadBytes); + Payload remotePayload = payloadParser.parse(result.getText()); gotRemotePayload = true; currentTask.connectAndRunProtocol(remotePayload); state.postValue(new AddContactState.QrCodeScanned()); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java index e69dc19de..745c59690 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotManager.java @@ -436,7 +436,7 @@ class HotspotManager { } private static String createWifiLoginString(String ssid, String password) { - // https://en.wikipedia.org/wiki/QR_code#WiFi_network_login + // https://en.wikipedia.org/wiki/QR_code#Joining_a_Wi%E2%80%91Fi_network // do not remove the dangling ';', it can cause problems to omit it return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;"; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/qrcode/QrCodeDecoder.java b/briar-android/src/main/java/org/briarproject/briar/android/qrcode/QrCodeDecoder.java index b5c96079a..a276d69b2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/qrcode/QrCodeDecoder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/qrcode/QrCodeDecoder.java @@ -41,7 +41,6 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback { private final ResultCallback callback; private Camera camera = null; - private int cameraIndex = 0; public QrCodeDecoder(AndroidExecutor androidExecutor, @IoExecutor Executor ioExecutor, ResultCallback callback) { @@ -53,14 +52,12 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback { @Override public void start(Camera camera, int cameraIndex) { this.camera = camera; - this.cameraIndex = cameraIndex; askForPreviewFrame(); } @Override public void stop() { camera = null; - cameraIndex = 0; } @UiThread