Factor out recognition of QR code format.

This commit is contained in:
akwizgran
2022-12-02 12:12:09 +00:00
parent 1b8d1a5a8d
commit badccac90c
28 changed files with 372 additions and 121 deletions

View File

@@ -1,18 +1,26 @@
package org.briarproject.bramble.api.keyagreement; package org.briarproject.bramble.api.keyagreement;
public interface KeyAgreementConstants { import org.briarproject.bramble.api.mailbox.MailboxConstants;
/** public interface KeyAgreementConstants {
* The version of the BQP protocol used in beta releases. This version
* number is reserved.
*/
byte BETA_PROTOCOL_VERSION = 89;
/** /**
* The current version of the BQP protocol. * The current version of the BQP protocol.
*/ */
byte PROTOCOL_VERSION = 4; 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. * The length of the BQP key commitment in bytes.
*/ */

View File

@@ -7,5 +7,5 @@ import java.io.IOException;
@NotNullByDefault @NotNullByDefault
public interface PayloadParser { public interface PayloadParser {
Payload parse(byte[] raw) throws IOException; Payload parse(String payload) throws IOException;
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import java.util.List; import java.util.List;
@@ -19,6 +20,18 @@ public interface MailboxConstants {
*/ */
TransportId ID = new TransportId("org.briarproject.bramble.mailbox"); 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 * Mailbox API versions that we support as a client. This is reported to our
* contacts by {@link MailboxUpdateManager}. * contacts by {@link MailboxUpdateManager}.

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
public abstract class MailboxPairingState { public abstract class MailboxPairingState {
public abstract static class Pending extends MailboxPairingState { public abstract static class Pending extends MailboxPairingState {
@@ -29,6 +31,14 @@ public abstract class MailboxPairingState {
} }
public static class InvalidQrCode extends 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 { public static class MailboxAlreadyPaired extends MailboxPairingState {

View File

@@ -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<QrCodeType, Integer> classifyQrCode(String payload);
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.util;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@@ -17,13 +16,18 @@ import javax.annotation.Nullable;
import static java.nio.charset.CodingErrorAction.IGNORE; import static java.nio.charset.CodingErrorAction.IGNORE;
import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.CASE_INSENSITIVE;
@SuppressWarnings("CharsetObjectCanBeUsed")
@NotNullByDefault @NotNullByDefault
public class StringUtils { public class StringUtils {
private static final Charset UTF_8 = Charset.forName("UTF-8"); public static final Charset UTF_8 = Charset.forName("UTF-8");
private static Pattern MAC = Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" + public static final Charset US_ASCII = Charset.forName("US-ASCII");
"[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}", public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
CASE_INSENSITIVE);
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[] { private static final char[] HEX = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '0', '1', '2', '3', '4', '5', '6', '7',
@@ -45,11 +49,7 @@ public class StringUtils {
} }
public static byte[] toUtf8(String s) { public static byte[] toUtf8(String s) {
try { return s.getBytes(UTF_8);
return s.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
} }
public static String fromUtf8(byte[] bytes) { public static String fromUtf8(byte[] bytes) {

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule; import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.qrcode.QrCodeModule;
import org.briarproject.bramble.record.RecordModule; import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.reliability.ReliabilityModule; import org.briarproject.bramble.reliability.ReliabilityModule;
import org.briarproject.bramble.rendezvous.RendezvousModule; import org.briarproject.bramble.rendezvous.RendezvousModule;
@@ -47,6 +48,7 @@ import dagger.Module;
MailboxModule.class, MailboxModule.class,
PluginModule.class, PluginModule.class,
PropertiesModule.class, PropertiesModule.class,
QrCodeModule.class,
RecordModule.class, RecordModule.class,
ReliabilityModule.class, ReliabilityModule.class,
RendezvousModule.class, RendezvousModule.class,

View File

@@ -19,7 +19,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -29,6 +28,7 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT; import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.util.LogUtils.logException; 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.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString; import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -99,7 +99,7 @@ class AccountManagerImpl implements AccountManager {
} }
try { try {
BufferedReader reader = new BufferedReader(new InputStreamReader( BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), Charset.forName("UTF-8"))); new FileInputStream(f), UTF_8));
String key = reader.readLine(); String key = reader.readLine();
reader.close(); reader.close();
return key; return key;
@@ -151,7 +151,7 @@ class AccountManagerImpl implements AccountManager {
@GuardedBy("stateChangeLock") @GuardedBy("stateChangeLock")
private void writeDbKeyToFile(String key, File f) throws IOException { private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f); FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes(Charset.forName("UTF-8"))); out.write(key.getBytes(UTF_8));
out.flush(); out.flush();
out.close(); out.close();
} }

View File

@@ -29,7 +29,6 @@ import org.briarproject.nullsafety.NotNullByDefault;
import org.whispersystems.curve25519.Curve25519; import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair; import org.whispersystems.curve25519.Curve25519KeyPair;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.Provider; 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.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
@NotNullByDefault @NotNullByDefault
class CryptoComponentImpl implements CryptoComponent { class CryptoComponentImpl implements CryptoComponent {
@@ -460,7 +460,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override @Override
public String encodeOnion(byte[] publicKey) { public String encodeOnion(byte[] publicKey) {
Digest digest = new SHA3Digest(256); 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(label, 0, label.length);
digest.update(publicKey, 0, publicKey.length); digest.update(publicKey, 0, publicKey.length);
digest.update(ONION_HS_PROTOCOL_VERSION); digest.update(ONION_HS_PROTOCOL_VERSION);

View File

@@ -39,12 +39,13 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.charset.Charset;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Scanner; import java.util.Scanner;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class MessageEncrypter { public class MessageEncrypter {
@@ -228,7 +229,7 @@ public class MessageEncrypter {
PublicKey publicKey = PublicKey publicKey =
encrypter.getKeyParser().parsePublicKey(keyBytes); encrypter.getKeyParser().parsePublicKey(keyBytes);
String message = readFully(System.in); 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); byte[] ciphertext = encrypter.encrypt(publicKey, plaintext);
System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH)); System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH));
} }
@@ -242,7 +243,7 @@ public class MessageEncrypter {
encrypter.getKeyParser().parsePrivateKey(keyBytes); encrypter.getKeyParser().parsePrivateKey(keyBytes);
byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in)); byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in));
byte[] plaintext = encrypter.decrypt(privateKey, ciphertext); 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 { private static String readFully(InputStream in) throws IOException {

View File

@@ -1,7 +1,5 @@
package org.briarproject.bramble.keyagreement; 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.KeyAgreementTask;
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;
@@ -19,13 +17,13 @@ public class KeyAgreementModule {
} }
@Provides @Provides
PayloadEncoder providePayloadEncoder(BdfWriterFactory bdfWriterFactory) { PayloadEncoder providePayloadEncoder(PayloadEncoderImpl payloadEncoder) {
return new PayloadEncoderImpl(bdfWriterFactory); return payloadEncoder;
} }
@Provides @Provides
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) { PayloadParser providePayloadParser(PayloadParserImpl payloadParser) {
return new PayloadParserImpl(bdfReaderFactory); return payloadParser;
} }
@Provides @Provides

View File

@@ -13,7 +13,8 @@ import java.io.IOException;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; 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 @Immutable
@NotNullByDefault @NotNullByDefault
@@ -29,7 +30,8 @@ class PayloadEncoderImpl implements PayloadEncoder {
@Override @Override
public byte[] encode(Payload p) { public byte[] encode(Payload p) {
ByteArrayOutputStream out = new ByteArrayOutputStream(); 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); BdfWriter w = bdfWriterFactory.createWriter(out);
try { try {
w.writeListStart(); // Payload start w.writeListStart(); // Payload start

View File

@@ -1,6 +1,7 @@
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.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException; 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;
@@ -11,6 +12,8 @@ import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
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;
import org.briarproject.bramble.api.plugin.TransportId; 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 org.briarproject.nullsafety.NotNullByDefault;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -21,34 +24,41 @@ import java.util.List;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; 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.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_BLUETOOTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN; 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 @Immutable
@NotNullByDefault @NotNullByDefault
class PayloadParserImpl implements PayloadParser { class PayloadParserImpl implements PayloadParser {
private final BdfReaderFactory bdfReaderFactory; private final BdfReaderFactory bdfReaderFactory;
private final QrCodeClassifier qrCodeClassifier;
@Inject @Inject
PayloadParserImpl(BdfReaderFactory bdfReaderFactory) { PayloadParserImpl(BdfReaderFactory bdfReaderFactory,
QrCodeClassifier qrCodeClassifier) {
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
this.qrCodeClassifier = qrCodeClassifier;
} }
@Override @Override
public Payload parse(byte[] raw) throws IOException { public Payload parse(String payloadString) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(raw); Pair<QrCodeType, Integer> typeAndVersion =
// First byte: the protocol version qrCodeClassifier.classifyQrCode(payloadString);
int protocolVersion = in.read(); if (typeAndVersion.getFirst() != BQP) throw new FormatException();
if (protocolVersion == -1) throw new FormatException(); int formatVersion = typeAndVersion.getSecond();
if (protocolVersion != PROTOCOL_VERSION) { if (formatVersion != QR_FORMAT_VERSION) {
boolean tooOld = protocolVersion < PROTOCOL_VERSION || boolean tooOld = formatVersion < QR_FORMAT_VERSION;
protocolVersion == BETA_PROTOCOL_VERSION;
throw new UnsupportedVersionException(tooOld); 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 // The rest of the payload is a BDF list with one or more elements
BdfReader r = bdfReaderFactory.createReader(in); BdfReader r = bdfReaderFactory.createReader(in);
BdfList payload = r.readList(); BdfList payload = r.readList();

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
@@ -25,6 +26,7 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
private final MailboxApi api; private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager; private final MailboxUpdateManager mailboxUpdateManager;
private final QrCodeClassifier qrCodeClassifier;
@Inject @Inject
MailboxPairingTaskFactoryImpl( MailboxPairingTaskFactoryImpl(
@@ -34,7 +36,8 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
Clock clock, Clock clock,
MailboxApi api, MailboxApi api,
MailboxSettingsManager mailboxSettingsManager, MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) { MailboxUpdateManager mailboxUpdateManager,
QrCodeClassifier qrCodeClassifier) {
this.eventExecutor = eventExecutor; this.eventExecutor = eventExecutor;
this.db = db; this.db = db;
this.crypto = crypto; this.crypto = crypto;
@@ -42,12 +45,13 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
this.api = api; this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager; this.mailboxUpdateManager = mailboxUpdateManager;
this.qrCodeClassifier = qrCodeClassifier;
} }
@Override @Override
public MailboxPairingTask createPairingTask(String qrCodePayload) { public MailboxPairingTask createPairingTask(String qrCodePayload) {
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db, return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
crypto, clock, api, mailboxSettingsManager, crypto, clock, api, mailboxSettingsManager,
mailboxUpdateManager); mailboxUpdateManager, qrCodeClassifier);
} }
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent; 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.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; 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.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException; import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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.Level.WARNING;
import static java.util.logging.Logger.getLogger; 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.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -40,9 +45,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final static Logger LOG = private final static Logger LOG =
getLogger(MailboxPairingTaskImpl.class.getName()); 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 String payload;
private final Executor eventExecutor; private final Executor eventExecutor;
@@ -52,6 +54,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final MailboxApi api; private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager; private final MailboxUpdateManager mailboxUpdateManager;
private final QrCodeClassifier qrCodeClassifier;
private final long timeStarted; private final long timeStarted;
private final Object lock = new Object(); private final Object lock = new Object();
@@ -69,7 +72,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
Clock clock, Clock clock,
MailboxApi api, MailboxApi api,
MailboxSettingsManager mailboxSettingsManager, MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) { MailboxUpdateManager mailboxUpdateManager,
QrCodeClassifier qrCodeClassifier) {
this.payload = payload; this.payload = payload;
this.eventExecutor = eventExecutor; this.eventExecutor = eventExecutor;
this.db = db; this.db = db;
@@ -78,6 +82,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
this.api = api; this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager; this.mailboxUpdateManager = mailboxUpdateManager;
this.qrCodeClassifier = qrCodeClassifier;
timeStarted = clock.currentTimeMillis(); timeStarted = clock.currentTimeMillis();
state = new MailboxPairingState.QrCodeReceived(timeStarted); state = new MailboxPairingState.QrCodeReceived(timeStarted);
} }
@@ -101,10 +106,20 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
@Override @Override
public void run() { public void run() {
Pair<QrCodeType, Integer> 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 { try {
pairMailbox(); pairMailbox();
} catch (FormatException e) { } catch (FormatException e) {
onMailboxError(e, new MailboxPairingState.InvalidQrCode()); onMailboxError(e, new MailboxPairingState.InvalidQrCode(qrCodeType,
formatVersion));
} catch (MailboxAlreadyPairedException e) { } catch (MailboxAlreadyPairedException e) {
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired()); onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired());
} catch (IOException e) { } catch (IOException e) {
@@ -169,14 +184,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
} }
throw new FormatException(); 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"); LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33); byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onion = crypto.encodeOnion(onionPubKey); String onion = crypto.encodeOnion(onionPubKey);

View File

@@ -46,7 +46,6 @@ import java.io.OutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion; 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; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -419,9 +419,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path); append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath(); String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
append(strb, "ClientTransportPlugin snowflake exec", snowflakePath); append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
//noinspection CharsetObjectCanBeUsed return new ByteArrayInputStream(strb.toString().getBytes(UTF_8));
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
} }
private void listFiles(File f) { private void listFiles(File f) {

View File

@@ -7,7 +7,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.encoders.Base64;
import org.briarproject.bramble.api.crypto.CryptoComponent; 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 { class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
@@ -31,6 +31,6 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC); EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
byte[] hash = spec.getH(); byte[] hash = spec.getH();
byte[] base64 = Base64.encode(hash); byte[] base64 = Base64.encode(hash);
return "ED25519-V3:" + new String(base64, Charset.forName("US-ASCII")); return "ED25519-V3:" + new String(base64, US_ASCII);
} }
} }

View File

@@ -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<QrCodeType, Integer> 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);
}
}

View File

@@ -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;
}
}

View File

@@ -20,7 +20,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.Charset;
import javax.annotation.Nullable; 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.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; 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.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString; import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
@@ -342,7 +342,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
private void storeDatabaseKey(File f, String hex) throws IOException { private void storeDatabaseKey(File f, String hex) throws IOException {
f.getParentFile().mkdirs(); f.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(f); FileOutputStream out = new FileOutputStream(f);
out.write(hex.getBytes(Charset.forName("UTF-8"))); out.write(hex.getBytes(UTF_8));
out.flush(); out.flush();
out.close(); out.close();
} }
@@ -350,7 +350,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
@Nullable @Nullable
private String loadDatabaseKey(File f) throws IOException { private String loadDatabaseKey(File f) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader( BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), Charset.forName("UTF-8"))); new FileInputStream(f), UTF_8));
String hex = reader.readLine(); String hex = reader.readLine();
reader.close(); reader.close();
return hex; return hex;

View File

@@ -2,21 +2,26 @@ 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.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException; 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.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
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;
import java.io.ByteArrayInputStream; 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.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.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -26,32 +31,29 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
private final BdfReaderFactory bdfReaderFactory = private final BdfReaderFactory bdfReaderFactory =
context.mock(BdfReaderFactory.class); context.mock(BdfReaderFactory.class);
private final QrCodeClassifier qrCodeClassifier =
context.mock(QrCodeClassifier.class);
private final BdfReader bdfReader = context.mock(BdfReader.class); private final BdfReader bdfReader = context.mock(BdfReader.class);
private final String payload = getRandomString(123);
private final PayloadParserImpl payloadParser = private final PayloadParserImpl payloadParser =
new PayloadParserImpl(bdfReaderFactory); new PayloadParserImpl(bdfReaderFactory, qrCodeClassifier);
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testThrowsFormatExceptionIfPayloadIsEmpty() throws Exception { public void testThrowsFormatExceptionForWrongQrCodeType() throws Exception {
payloadParser.parse(new byte[0]); expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION);
payloadParser.parse(payload);
} }
@Test @Test
public void testThrowsUnsupportedVersionExceptionForOldVersion() public void testThrowsUnsupportedVersionExceptionForOldVersion()
throws Exception { throws Exception {
try { expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION - 1);
payloadParser.parse(new byte[] {PROTOCOL_VERSION - 1});
fail();
} catch (UnsupportedVersionException e) {
assertTrue(e.isTooOld());
}
}
@Test
public void testThrowsUnsupportedVersionExceptionForBetaVersion()
throws Exception {
try { try {
payloadParser.parse(new byte[] {BETA_PROTOCOL_VERSION}); payloadParser.parse(payload);
fail(); fail();
} catch (UnsupportedVersionException e) { } catch (UnsupportedVersionException e) {
assertTrue(e.isTooOld()); assertTrue(e.isTooOld());
@@ -61,8 +63,10 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
@Test @Test
public void testThrowsUnsupportedVersionExceptionForNewVersion() public void testThrowsUnsupportedVersionExceptionForNewVersion()
throws Exception { throws Exception {
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION + 1);
try { try {
payloadParser.parse(new byte[] {PROTOCOL_VERSION + 1}); payloadParser.parse(payload);
fail(); fail();
} catch (UnsupportedVersionException e) { } catch (UnsupportedVersionException e) {
assertFalse(e.isTooOld()); assertFalse(e.isTooOld());
@@ -71,6 +75,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testThrowsFormatExceptionForEmptyList() throws Exception { public void testThrowsFormatExceptionForEmptyList() throws Exception {
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -79,7 +85,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(new BdfList())); will(returnValue(new BdfList()));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
@@ -87,6 +93,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH); byte[] commitment = getRandomBytes(COMMIT_LENGTH);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -97,7 +105,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(false)); will(returnValue(false));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
@@ -105,6 +113,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH - 1); byte[] commitment = getRandomBytes(COMMIT_LENGTH - 1);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -115,7 +124,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
@@ -123,6 +132,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH + 1); byte[] commitment = getRandomBytes(COMMIT_LENGTH + 1);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -133,12 +143,14 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test @Test
public void testAcceptsPayloadWithNoDescriptors() throws Exception { public void testAcceptsPayloadWithNoDescriptors() throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH); byte[] commitment = getRandomBytes(COMMIT_LENGTH);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -149,8 +161,16 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
Payload p = payloadParser.parse(new byte[] {PROTOCOL_VERSION}); Payload p = payloadParser.parse(payload);
assertArrayEquals(commitment, p.getCommitment()); assertArrayEquals(commitment, p.getCommitment());
assertTrue(p.getTransportDescriptors().isEmpty()); 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)));
}});
}
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent; 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.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion; 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.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
@@ -27,6 +30,9 @@ import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Collections.singletonList; 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.mailbox.MailboxTestUtils.getQrCodePayload;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
@@ -48,9 +54,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
context.mock(MailboxSettingsManager.class); context.mock(MailboxSettingsManager.class);
private final MailboxUpdateManager mailboxUpdateManager = private final MailboxUpdateManager mailboxUpdateManager =
context.mock(MailboxUpdateManager.class); context.mock(MailboxUpdateManager.class);
private final MailboxPairingTaskFactory factory = private final QrCodeClassifier qrCodeClassifier =
new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api, context.mock(QrCodeClassifier.class);
mailboxSettingsManager, mailboxUpdateManager);
private final String onion = getRandomString(56); private final String onion = getRandomString(56);
private final byte[] onionBytes = getRandomBytes(32); private final byte[] onionBytes = getRandomBytes(32);
@@ -75,23 +80,47 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testInvalidQrCode() { public void testInvalidQrCodeType() {
MailboxPairingTask task1 = createPairingTask(getRandomString(42)); String payload = getRandomString(65);
task1.run(); MailboxPairingTask task = createPairingTask(payload);
task1.addObserver(state ->
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
task.run();
task.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode) assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
); );
}
String goodLength = "00" + getRandomString(63); @Test
MailboxPairingTask task2 = createPairingTask(goodLength); public void testInvalidQrCodeVersion() {
task2.run(); String payload = getRandomString(65);
task2.addObserver(state -> 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) assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
); );
} }
@Test @Test
public void testSuccessfulPairing() throws Exception { public void testSuccessfulPairing() throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
@@ -156,6 +185,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
private void testApiException(Exception e, private void testApiException(Exception e,
Class<? extends MailboxPairingState> s) throws Exception { Class<? extends MailboxPairingState> s) throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
@@ -170,6 +200,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
@Test @Test
public void testDbException() throws Exception { public void testDbException() throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
@@ -206,6 +237,16 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
will(returnValue(time)); 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)));
}});
} }
} }

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.WeakSingletonProvider; import org.briarproject.bramble.api.WeakSingletonProvider;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.net.SocketFactory; import javax.net.SocketFactory;
@@ -11,20 +10,24 @@ import javax.net.SocketFactory;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import static java.util.concurrent.TimeUnit.MILLISECONDS; 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.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
class MailboxTestUtils { class MailboxTestUtils {
static String getQrCodePayload(byte[] onionBytes, byte[] setupToken) { static String getQrCodePayload(byte[] onionBytes, byte[] setupToken) {
int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION;
byte[] payloadBytes = ByteBuffer.allocate(65) byte[] payloadBytes = ByteBuffer.allocate(65)
.put((byte) 32) // 1 .put((byte) formatIdAndVersion) // 1
.put(onionBytes) // 32 .put(onionBytes) // 32
.put(setupToken) // 32 .put(setupToken) // 32
.array(); .array();
//noinspection CharsetObjectCanBeUsed return new String(payloadBytes, ISO_8859_1);
return new String(payloadBytes, Charset.forName("ISO-8859-1"));
} }
// Used by mailbox integration tests
static String getQrCodePayload(byte[] setupToken) { static String getQrCodePayload(byte[] setupToken) {
return getQrCodePayload(getRandomId(), setupToken); return getQrCodePayload(getRandomId(), setupToken);
} }

View File

@@ -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<QrCodeType, Integer> 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<QrCodeType, Integer> 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<QrCodeType, Integer> 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<QrCodeType, Integer> result = classifier.classifyQrCode(payload);
assertEquals(UNKNOWN, result.getFirst());
assertEquals(0, result.getSecond().intValue());
}
}

View File

@@ -12,7 +12,6 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetDecoder;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore; 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_RXCLEAR;
import static jssc.SerialPort.PURGE_TXCLEAR; import static jssc.SerialPort.PURGE_TXCLEAR;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -41,7 +41,6 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ModemImpl.class.getName()); 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 MAX_LINE_LENGTH = 256;
private static final int[] BAUD_RATES = { private static final int[] BAUD_RATES = {
256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200 256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200

View File

@@ -60,7 +60,6 @@ import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; 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.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; 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.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.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.NO_ADAPTER;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
@@ -126,9 +126,6 @@ class AddNearbyContactViewModel extends AndroidViewModel
REFUSED REFUSED
} }
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private final EventBus eventBus; private final EventBus eventBus;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -446,10 +443,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
// Ignore results until the KeyAgreementTask is ready // Ignore results until the KeyAgreementTask is ready
if (!gotLocalPayload || gotRemotePayload || currentTask == null) return; if (!gotLocalPayload || gotRemotePayload || currentTask == null) return;
try { try {
byte[] payloadBytes = result.getText().getBytes(ISO_8859_1); Payload remotePayload = payloadParser.parse(result.getText());
if (LOG.isLoggable(INFO))
LOG.info("Remote payload is " + payloadBytes.length + " bytes");
Payload remotePayload = payloadParser.parse(payloadBytes);
gotRemotePayload = true; gotRemotePayload = true;
currentTask.connectAndRunProtocol(remotePayload); currentTask.connectAndRunProtocol(remotePayload);
state.postValue(new AddContactState.QrCodeScanned()); state.postValue(new AddContactState.QrCodeScanned());

View File

@@ -436,7 +436,7 @@ class HotspotManager {
} }
private static String createWifiLoginString(String ssid, String password) { 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 // do not remove the dangling ';', it can cause problems to omit it
return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;"; return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;";
} }

View File

@@ -41,7 +41,6 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private final ResultCallback callback; private final ResultCallback callback;
private Camera camera = null; private Camera camera = null;
private int cameraIndex = 0;
public QrCodeDecoder(AndroidExecutor androidExecutor, public QrCodeDecoder(AndroidExecutor androidExecutor,
@IoExecutor Executor ioExecutor, ResultCallback callback) { @IoExecutor Executor ioExecutor, ResultCallback callback) {
@@ -53,14 +52,12 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
@Override @Override
public void start(Camera camera, int cameraIndex) { public void start(Camera camera, int cameraIndex) {
this.camera = camera; this.camera = camera;
this.cameraIndex = cameraIndex;
askForPreviewFrame(); askForPreviewFrame();
} }
@Override @Override
public void stop() { public void stop() {
camera = null; camera = null;
cameraIndex = 0;
} }
@UiThread @UiThread