Recovery UI

This commit is contained in:
ameba23
2021-04-16 21:14:34 +02:00
parent b3adfe19a4
commit 3ff6042d10
5 changed files with 106 additions and 32 deletions

View File

@@ -1,8 +1,9 @@
package org.briarproject.briar.android.socialbackup; package org.briarproject.briar.android.socialbackup;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.socialbackup.Shard;
import org.briarproject.briar.api.socialbackup.DarkCrystal; import org.briarproject.briar.api.socialbackup.DarkCrystal;
import org.briarproject.briar.api.socialbackup.Shard;
import org.magmacollective.darkcrystal.secretsharingwrapper.SecretSharingWrapper; import org.magmacollective.darkcrystal.secretsharingwrapper.SecretSharingWrapper;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@@ -13,14 +14,12 @@ import java.util.Random;
import javax.inject.Inject; import javax.inject.Inject;
import dagger.Provides;
import static org.briarproject.briar.socialbackup.SocialBackupConstants.SECRET_ID_BYTES; import static org.briarproject.briar.socialbackup.SocialBackupConstants.SECRET_ID_BYTES;
@NotNullByDefault @NotNullByDefault
public class DarkCrystalImpl implements DarkCrystal { public class DarkCrystalImpl implements DarkCrystal {
@Inject @Inject
DarkCrystalImpl() { DarkCrystalImpl() {
} }
@@ -30,7 +29,8 @@ public class DarkCrystalImpl implements DarkCrystal {
Random random = new Random(); Random random = new Random();
byte[] secretId = new byte[SECRET_ID_BYTES]; byte[] secretId = new byte[SECRET_ID_BYTES];
random.nextBytes(secretId); random.nextBytes(secretId);
List<byte[]> shardsBytes = SecretSharingWrapper.share(secret.getBytes(), numShards, threshold); List<byte[]> shardsBytes = SecretSharingWrapper
.share(secret.getBytes(), numShards, threshold);
List<Shard> shards = new ArrayList<>(numShards); List<Shard> shards = new ArrayList<>(numShards);
for (byte[] shardBytes : shardsBytes) { for (byte[] shardBytes : shardsBytes) {
shards.add(new Shard(secretId, shardBytes)); shards.add(new Shard(secretId, shardBytes));
@@ -44,9 +44,10 @@ public class DarkCrystalImpl implements DarkCrystal {
// Check each shard has the same secret Id // Check each shard has the same secret Id
byte[] secretId = shards.get(0).getSecretId(); byte[] secretId = shards.get(0).getSecretId();
for (Shard shard : shards) { for (Shard shard : shards) {
if (!Arrays.equals(shard.getSecretId(), secretId)) throw new GeneralSecurityException(); if (!Arrays.equals(shard.getSecretId(), secretId))
throw new GeneralSecurityException();
} }
List<byte[]> shardsBytes = new ArrayList<>(shards.size()); List<byte[]> shardsBytes = new ArrayList<>(shards.size());
for (Shard shard : shards) { for (Shard shard : shards) {
shardsBytes.add(shard.getShard()); shardsBytes.add(shard.getShard());
} }

View File

@@ -12,6 +12,7 @@ import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask; import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
import java.io.IOException;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -53,6 +54,11 @@ public class CustodianReturnShardActivity extends BriarActivity
try { try {
viewModel.start(contactId); viewModel.start(contactId);
} catch (IOException e) {
// TODO improve this
Toast.makeText(this,
"It looks like you are not connected to a Wifi network",
Toast.LENGTH_SHORT).show();
} catch (DbException e) { } catch (DbException e) {
Toast.makeText(this, Toast.makeText(this,
"You do not hold a backup piece for this contact", "You do not hold a backup piece for this contact",

View File

@@ -1,6 +1,8 @@
package org.briarproject.briar.android.socialbackup.recover; package org.briarproject.briar.android.socialbackup.recover;
import android.app.Application; import android.app.Application;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.widget.Toast; import android.widget.Toast;
import com.google.zxing.Result; import com.google.zxing.Result;
@@ -17,6 +19,9 @@ import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.socialbackup.SocialBackupManager; import org.briarproject.briar.api.socialbackup.SocialBackupManager;
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask; import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset; 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;
@@ -29,8 +34,8 @@ import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static android.content.Context.WIFI_SERVICE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
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;
@@ -48,6 +53,7 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
final QrCodeDecoder qrCodeDecoder; final QrCodeDecoder qrCodeDecoder;
private boolean wasContinueClicked = false; private boolean wasContinueClicked = false;
private boolean qrCodeRead = false; private boolean qrCodeRead = false;
private WifiManager wifiManager;
private final MutableLiveEvent<Boolean> showCameraFragment = private final MutableLiveEvent<Boolean> showCameraFragment =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> successDismissed = private final MutableLiveEvent<Boolean> successDismissed =
@@ -62,13 +68,13 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
@Inject @Inject
public CustodianReturnShardViewModel( public CustodianReturnShardViewModel(
@NonNull Application application, @NonNull Application app,
@IoExecutor Executor ioExecutor, @IoExecutor Executor ioExecutor,
SocialBackupManager socialBackupManager, SocialBackupManager socialBackupManager,
DatabaseComponent db, DatabaseComponent db,
CustodianTask task, CustodianTask task,
AndroidExecutor androidExecutor) { AndroidExecutor androidExecutor) {
super(application); super(app);
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
@@ -76,19 +82,50 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
this.db = db; this.db = db;
this.task = task; this.task = task;
qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this); qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this);
wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE);
} }
public void start(ContactId contactId) throws DbException { private InetAddress getWifiIpv4Address() {
db.transaction(false, txn -> { if (wifiManager == null) return null;
if (!socialBackupManager.amCustodian(txn, contactId)) { // If we're connected to a wifi network, return its address
throw new DbException(); WifiInfo info = wifiManager.getConnectionInfo();
} if (info != null && info.getIpAddress() != 0) {
returnShardPayloadBytes = socialBackupManager return intToInetAddress(info.getIpAddress());
.getReturnShardPayloadBytes(txn, contactId); }
}); return null;
task.cancel(); }
task.start(this, returnShardPayloadBytes);
} // TODO this is not the right place for this
private InetAddress intToInetAddress(int ip) {
byte[] ipBytes = new byte[4];
ipBytes[0] = (byte) (ip & 0xFF);
ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
try {
return InetAddress.getByAddress(ipBytes);
} catch (UnknownHostException e) {
// Should only be thrown if address has illegal length
throw new AssertionError(e);
}
}
public void start(ContactId contactId) throws DbException, IOException {
InetAddress inetAddress = getWifiIpv4Address();
LOG.info("Client InetAddress: " + inetAddress);
if (inetAddress == null)
throw new IOException("Cannot get IP on local wifi");
db.transaction(false, txn -> {
if (!socialBackupManager.amCustodian(txn, contactId)) {
throw new DbException();
}
returnShardPayloadBytes = socialBackupManager
.getReturnShardPayloadBytes(txn, contactId);
});
task.cancel();
task.start(this, returnShardPayloadBytes);
}
@IoExecutor @IoExecutor
@Override @Override
@@ -121,7 +158,7 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
@UiThread @UiThread
public void onSuccessDismissed() { public void onSuccessDismissed() {
successDismissed.setEvent(true); successDismissed.setEvent(true);
} }
@@ -136,6 +173,7 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
LiveEvent<Boolean> getSuccessDismissed() { LiveEvent<Boolean> getSuccessDismissed() {
return successDismissed; return successDismissed;
} }
LiveData<CustodianTask.State> getState() { LiveData<CustodianTask.State> getState() {
return state; return state;
} }
@@ -144,8 +182,8 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
public void onStateChanged(CustodianTask.State state) { public void onStateChanged(CustodianTask.State state) {
this.state.postValue(state); this.state.postValue(state);
// Connecting, SendingShard, ReceivingAck, Success, Failure // Connecting, SendingShard, ReceivingAck, Success, Failure
if (state instanceof CustodianTask.State.SendingShard) { if (state instanceof CustodianTask.State.SendingShard) {
qrCodeRead = true; qrCodeRead = true;
} }
} }
} }

View File

@@ -61,7 +61,10 @@ public class OwnerReturnShardActivity extends BaseActivity
showInitialFragment(new OwnerRecoveryModeExplainerFragment()); showInitialFragment(new OwnerRecoveryModeExplainerFragment());
} }
viewModel.getShowQrCodeFragment().observeEvent(this, show -> { viewModel.getShowQrCodeFragment().observeEvent(this, show -> {
if (show) showQrCodeFragment(); if (show) {
viewModel.startListening();
showQrCodeFragment();
}
}); });
viewModel.getStartClicked().observeEvent(this, start -> { viewModel.getStartClicked().observeEvent(this, start -> {
if (start) { if (start) {
@@ -132,6 +135,10 @@ public class OwnerReturnShardActivity extends BaseActivity
Toast.makeText(this, Toast.makeText(this,
"Success - got shard" + (added ? "" : " duplicate"), "Success - got shard" + (added ? "" : " duplicate"),
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
if (added && viewModel.canRecover()) {
}
onBackPressed();
// finish(); // finish();
} else if (state instanceof SecretOwnerTask.State.Failure) { } else if (state instanceof SecretOwnerTask.State.Failure) {
// TODO error screen, handle reason // TODO error screen, handle reason

View File

@@ -6,18 +6,22 @@ import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.contact.add.nearby.QrCodeUtils; import org.briarproject.briar.android.contact.add.nearby.QrCodeUtils;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.socialbackup.DarkCrystal;
import org.briarproject.briar.api.socialbackup.ReturnShardPayload; import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
import org.briarproject.briar.api.socialbackup.Shard;
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask; import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -48,6 +52,7 @@ class OwnerReturnShardViewModel extends AndroidViewModel
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Executor ioExecutor; private final Executor ioExecutor;
private final SecretOwnerTask task; private final SecretOwnerTask task;
private final DarkCrystal darkCrystal;
private final MutableLiveEvent<Boolean> showQrCodeFragment = private final MutableLiveEvent<Boolean> showQrCodeFragment =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
@@ -65,15 +70,16 @@ class OwnerReturnShardViewModel extends AndroidViewModel
OwnerReturnShardViewModel(Application app, OwnerReturnShardViewModel(Application app,
AndroidExecutor androidExecutor, AndroidExecutor androidExecutor,
SecretOwnerTask task, SecretOwnerTask task,
DarkCrystal darkCrystal,
@IoExecutor Executor ioExecutor) { @IoExecutor Executor ioExecutor) {
super(app); super(app);
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.darkCrystal = darkCrystal;
this.task = task; this.task = task;
wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE); wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE);
// IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); // IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED);
startListening();
} }
private InetAddress getWifiIpv4Address() { private InetAddress getWifiIpv4Address() {
@@ -86,6 +92,7 @@ class OwnerReturnShardViewModel extends AndroidViewModel
return null; return null;
} }
// TODO this is not the right place for this
private InetAddress intToInetAddress(int ip) { private InetAddress intToInetAddress(int ip) {
byte[] ipBytes = new byte[4]; byte[] ipBytes = new byte[4];
ipBytes[0] = (byte) (ip & 0xFF); ipBytes[0] = (byte) (ip & 0xFF);
@@ -128,7 +135,7 @@ class OwnerReturnShardViewModel extends AndroidViewModel
} }
@UiThread @UiThread
private void startListening() { public void startListening() {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
task.cancel(); task.cancel();
// wait until really cancelled // wait until really cancelled
@@ -188,7 +195,6 @@ class OwnerReturnShardViewModel extends AndroidViewModel
@Override @Override
public void onStateChanged(SecretOwnerTask.State state) { public void onStateChanged(SecretOwnerTask.State state) {
this.state.postValue(state);
if (state instanceof SecretOwnerTask.State.Listening) { if (state instanceof SecretOwnerTask.State.Listening) {
DisplayMetrics dm = DisplayMetrics dm =
getApplication().getResources().getDisplayMetrics(); getApplication().getResources().getDisplayMetrics();
@@ -196,13 +202,16 @@ class OwnerReturnShardViewModel extends AndroidViewModel
byte[] payloadBytes = ((SecretOwnerTask.State.Listening) state) byte[] payloadBytes = ((SecretOwnerTask.State.Listening) state)
.getLocalPayload(); .getLocalPayload();
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Local payload is " + payloadBytes.length LOG.info("Local QR code payload is " + payloadBytes.length
+ " bytes"); + " bytes");
} }
// Use ISO 8859-1 to encode bytes directly as a string // Use ISO 8859-1 to encode bytes directly as a string
String content = new String(payloadBytes, ISO_8859_1); String content = new String(payloadBytes, ISO_8859_1);
qrCodeBitmap = QrCodeUtils.createQrCode(dm, content); qrCodeBitmap = QrCodeUtils.createQrCode(dm, content);
this.state.postValue(state);
}); });
} else {
this.state.postValue(state);
} }
} }
@@ -220,7 +229,20 @@ class OwnerReturnShardViewModel extends AndroidViewModel
} }
public boolean canRecover() { public boolean canRecover() {
// TODO ArrayList<Shard> shards = new ArrayList();
return false; for (ReturnShardPayload returnShardPayload : recoveredShards) {
// TODO check shards all have same secret id
shards.add(returnShardPayload.getShard());
}
SecretKey secretKey;
try {
secretKey = darkCrystal.combineShards(shards);
} catch (GeneralSecurityException e) {
// TODO handle error message
return false;
}
// TODO find backup with highest version number
// recoveredShards.get(0).getBackupPayload();
return true;
} }
} }