diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DarkCrystalImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DarkCrystalImpl.java index b7cee74a8..d8b97b007 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DarkCrystalImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DarkCrystalImpl.java @@ -1,8 +1,9 @@ package org.briarproject.briar.android.socialbackup; + import org.briarproject.bramble.api.crypto.SecretKey; 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.Shard; import org.magmacollective.darkcrystal.secretsharingwrapper.SecretSharingWrapper; import java.security.GeneralSecurityException; @@ -13,14 +14,12 @@ import java.util.Random; import javax.inject.Inject; -import dagger.Provides; - import static org.briarproject.briar.socialbackup.SocialBackupConstants.SECRET_ID_BYTES; @NotNullByDefault public class DarkCrystalImpl implements DarkCrystal { - @Inject + @Inject DarkCrystalImpl() { } @@ -30,7 +29,8 @@ public class DarkCrystalImpl implements DarkCrystal { Random random = new Random(); byte[] secretId = new byte[SECRET_ID_BYTES]; random.nextBytes(secretId); - List shardsBytes = SecretSharingWrapper.share(secret.getBytes(), numShards, threshold); + List shardsBytes = SecretSharingWrapper + .share(secret.getBytes(), numShards, threshold); List shards = new ArrayList<>(numShards); for (byte[] shardBytes : shardsBytes) { shards.add(new Shard(secretId, shardBytes)); @@ -44,9 +44,10 @@ public class DarkCrystalImpl implements DarkCrystal { // Check each shard has the same secret Id byte[] secretId = shards.get(0).getSecretId(); for (Shard shard : shards) { - if (!Arrays.equals(shard.getSecretId(), secretId)) throw new GeneralSecurityException(); + if (!Arrays.equals(shard.getSecretId(), secretId)) + throw new GeneralSecurityException(); } - List shardsBytes = new ArrayList<>(shards.size()); + List shardsBytes = new ArrayList<>(shards.size()); for (Shard shard : shards) { shardsBytes.add(shard.getShard()); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardActivity.java index bd95f3260..3346e1ce0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardActivity.java @@ -12,6 +12,7 @@ import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.api.socialbackup.recovery.CustodianTask; +import java.io.IOException; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -53,6 +54,11 @@ public class CustodianReturnShardActivity extends BriarActivity try { 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) { Toast.makeText(this, "You do not hold a backup piece for this contact", diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java index f2614f42d..d3970c2ed 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/CustodianReturnShardViewModel.java @@ -1,6 +1,8 @@ package org.briarproject.briar.android.socialbackup.recover; import android.app.Application; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.widget.Toast; 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.recovery.CustodianTask; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.nio.charset.Charset; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -29,8 +34,8 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import static android.content.Context.WIFI_SERVICE; 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.WARNING; import static java.util.logging.Logger.getLogger; @@ -48,6 +53,7 @@ public class CustodianReturnShardViewModel extends AndroidViewModel final QrCodeDecoder qrCodeDecoder; private boolean wasContinueClicked = false; private boolean qrCodeRead = false; + private WifiManager wifiManager; private final MutableLiveEvent showCameraFragment = new MutableLiveEvent<>(); private final MutableLiveEvent successDismissed = @@ -62,13 +68,13 @@ public class CustodianReturnShardViewModel extends AndroidViewModel @Inject public CustodianReturnShardViewModel( - @NonNull Application application, + @NonNull Application app, @IoExecutor Executor ioExecutor, SocialBackupManager socialBackupManager, DatabaseComponent db, CustodianTask task, AndroidExecutor androidExecutor) { - super(application); + super(app); this.androidExecutor = androidExecutor; this.ioExecutor = ioExecutor; @@ -76,19 +82,50 @@ public class CustodianReturnShardViewModel extends AndroidViewModel this.db = db; this.task = task; qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this); + wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE); } - public void start(ContactId contactId) throws DbException { - db.transaction(false, txn -> { - if (!socialBackupManager.amCustodian(txn, contactId)) { - throw new DbException(); - } - returnShardPayloadBytes = socialBackupManager - .getReturnShardPayloadBytes(txn, contactId); - }); - task.cancel(); - task.start(this, returnShardPayloadBytes); - } + private InetAddress getWifiIpv4Address() { + if (wifiManager == null) return null; + // If we're connected to a wifi network, return its address + WifiInfo info = wifiManager.getConnectionInfo(); + if (info != null && info.getIpAddress() != 0) { + return intToInetAddress(info.getIpAddress()); + } + return null; + } + + // 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 @Override @@ -121,7 +158,7 @@ public class CustodianReturnShardViewModel extends AndroidViewModel @UiThread public void onSuccessDismissed() { - successDismissed.setEvent(true); + successDismissed.setEvent(true); } @@ -136,6 +173,7 @@ public class CustodianReturnShardViewModel extends AndroidViewModel LiveEvent getSuccessDismissed() { return successDismissed; } + LiveData getState() { return state; } @@ -144,8 +182,8 @@ public class CustodianReturnShardViewModel extends AndroidViewModel public void onStateChanged(CustodianTask.State state) { this.state.postValue(state); // Connecting, SendingShard, ReceivingAck, Success, Failure - if (state instanceof CustodianTask.State.SendingShard) { - qrCodeRead = true; - } + if (state instanceof CustodianTask.State.SendingShard) { + qrCodeRead = true; + } } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java index 2cb02f3a4..feb0c804e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardActivity.java @@ -61,7 +61,10 @@ public class OwnerReturnShardActivity extends BaseActivity showInitialFragment(new OwnerRecoveryModeExplainerFragment()); } viewModel.getShowQrCodeFragment().observeEvent(this, show -> { - if (show) showQrCodeFragment(); + if (show) { + viewModel.startListening(); + showQrCodeFragment(); + } }); viewModel.getStartClicked().observeEvent(this, start -> { if (start) { @@ -132,6 +135,10 @@ public class OwnerReturnShardActivity extends BaseActivity Toast.makeText(this, "Success - got shard" + (added ? "" : " duplicate"), Toast.LENGTH_SHORT).show(); + if (added && viewModel.canRecover()) { + + } + onBackPressed(); // finish(); } else if (state instanceof SecretOwnerTask.State.Failure) { // TODO error screen, handle reason diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java index 85881fe5b..02911e851 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/OwnerReturnShardViewModel.java @@ -6,18 +6,22 @@ import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.util.DisplayMetrics; +import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.briar.android.contact.add.nearby.QrCodeUtils; import org.briarproject.briar.android.viewmodel.LiveEvent; 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.Shard; import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.charset.Charset; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -48,6 +52,7 @@ class OwnerReturnShardViewModel extends AndroidViewModel private final AndroidExecutor androidExecutor; private final Executor ioExecutor; private final SecretOwnerTask task; + private final DarkCrystal darkCrystal; private final MutableLiveEvent showQrCodeFragment = new MutableLiveEvent<>(); @@ -65,15 +70,16 @@ class OwnerReturnShardViewModel extends AndroidViewModel OwnerReturnShardViewModel(Application app, AndroidExecutor androidExecutor, SecretOwnerTask task, + DarkCrystal darkCrystal, @IoExecutor Executor ioExecutor) { super(app); this.androidExecutor = androidExecutor; this.ioExecutor = ioExecutor; + this.darkCrystal = darkCrystal; this.task = task; wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE); // IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); - startListening(); } private InetAddress getWifiIpv4Address() { @@ -86,6 +92,7 @@ class OwnerReturnShardViewModel extends AndroidViewModel return null; } + // TODO this is not the right place for this private InetAddress intToInetAddress(int ip) { byte[] ipBytes = new byte[4]; ipBytes[0] = (byte) (ip & 0xFF); @@ -128,7 +135,7 @@ class OwnerReturnShardViewModel extends AndroidViewModel } @UiThread - private void startListening() { + public void startListening() { ioExecutor.execute(() -> { task.cancel(); // wait until really cancelled @@ -188,7 +195,6 @@ class OwnerReturnShardViewModel extends AndroidViewModel @Override public void onStateChanged(SecretOwnerTask.State state) { - this.state.postValue(state); if (state instanceof SecretOwnerTask.State.Listening) { DisplayMetrics dm = getApplication().getResources().getDisplayMetrics(); @@ -196,13 +202,16 @@ class OwnerReturnShardViewModel extends AndroidViewModel byte[] payloadBytes = ((SecretOwnerTask.State.Listening) state) .getLocalPayload(); if (LOG.isLoggable(INFO)) { - LOG.info("Local payload is " + payloadBytes.length + LOG.info("Local QR code payload is " + payloadBytes.length + " bytes"); } // Use ISO 8859-1 to encode bytes directly as a string String content = new String(payloadBytes, ISO_8859_1); 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() { - // TODO - return false; + ArrayList shards = new ArrayList(); + 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; } }