diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java index d1eb4e913..ed645e6e7 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java @@ -50,7 +50,8 @@ public interface ContactManager { boolean active) throws DbException; ContactId addContact(Transaction txn, Author remote, AuthorId local, - PublicKey handshake, boolean verified) throws DbException; + PublicKey handshake, boolean verified) + throws DbException, GeneralSecurityException; /** * Stores a contact associated with the given local and remote pseudonyms, @@ -209,6 +210,19 @@ public interface ContactManager { void setContactAlias(ContactId c, @Nullable String alias) throws DbException; + /** + * Sets the contact's handshake public key + */ + void setHandshakePublicKey(Transaction txn, ContactId c, + PublicKey handshakePublicKey) throws DbException, + GeneralSecurityException; + + /** + * Sets the contact's handshake public key + */ + void setHandshakePublicKey(ContactId c, PublicKey handshakePublicKey) + throws DbException, GeneralSecurityException; + /** * Returns true if a contact with this {@code remoteAuthorId} belongs to * the local pseudonym with this {@code localAuthorId}. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java index 7c525bcd3..ef143d269 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java @@ -546,6 +546,11 @@ public interface DatabaseComponent extends TransactionManager { void setContactAlias(Transaction txn, ContactId c, @Nullable String alias) throws DbException; + /** + * Sets the remote handshake public key for a given contact + */ + void setHandshakePublicKey(Transaction txn, ContactId c, PublicKey handshakePublicKey) throws DbException; + /** * Sets the given group's visibility to the given contact. */ diff --git a/bramble-core/build.gradle b/bramble-core/build.gradle index 00fd97c19..8fd6dfd4c 100644 --- a/bramble-core/build.gradle +++ b/bramble-core/build.gradle @@ -33,11 +33,11 @@ dependencies { } animalsniffer { - // Allow requireNonNull: Android desugaring rewrites it (so it's safe for us to use), - // and it gets used when passing method references instead of lambdas with Java 11. - // Note that this line allows *all* methods from java.util.Objects. - // That's the best that we can do with the configuration options that Animal Sniffer offers. - ignore 'java.util.Objects' + // Allow requireNonNull: Android desugaring rewrites it (so it's safe for us to use), + // and it gets used when passing method references instead of lambdas with Java 11. + // Note that this line allows *all* methods from java.util.Objects. + // That's the best that we can do with the configuration options that Animal Sniffer offers. + ignore 'java.util.Objects' } // needed to make test output available to bramble-java diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingDuplexSyncConnection.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingDuplexSyncConnection.java index 6e9c53e18..052f486d9 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingDuplexSyncConnection.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/IncomingDuplexSyncConnection.java @@ -3,7 +3,6 @@ package org.briarproject.bramble.connection; import org.briarproject.bramble.api.connection.ConnectionRegistry; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.HandshakeManager; -import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; @@ -50,7 +49,8 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection @Override public void run() { - LOG.info("Running IncomingDuplexSyncConnection"); + LOG.info("Running IncomingDuplexSyncConnection on transport " + + transportId.getString()); // Read and recognise the tag StreamContext ctx = recogniseTag(reader, transportId); if (ctx == null) { @@ -65,10 +65,10 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection return; } if (ctx.isHandshakeMode()) { - if (!performHandshake(ctx, contactId)) { - LOG.warning("Handshake failed"); - return; - } + if (!performHandshake(ctx, contactId)) { + LOG.warning("Handshake failed"); + return; + } // Allocate a rotation mode stream context ctx = allocateStreamContext(contactId, transportId); if (ctx == null) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java index 382e6e23f..000150287 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java @@ -61,7 +61,8 @@ class OutgoingDuplexSyncConnection extends DuplexSyncConnection @Override public void run() { - LOG.info("Running OutgoingDuplexSyncConnection"); + LOG.info("Running OutgoingDuplexSyncConnection on transport " + + transportId.getString()); // Allocate a stream context StreamContext ctx = allocateStreamContext(contactId, transportId); if (ctx == null) { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java index f7992aa66..f1882fbbe 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java @@ -9,6 +9,7 @@ import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent; +import org.briarproject.bramble.api.crypto.CryptoConstants; import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.SecretKey; @@ -121,9 +122,12 @@ class ContactManagerImpl implements ContactManager, EventListener { @Override public ContactId addContact(Transaction txn, Author remote, AuthorId local, - PublicKey handshake, boolean verified) throws DbException { + PublicKey handshake, boolean verified) + throws DbException, GeneralSecurityException { ContactId c = db.addContact(txn, remote, local, handshake, verified); Contact contact = db.getContact(txn, c); + KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn); + keyManager.addContact(txn, c, handshake, ourKeyPair); for (ContactHook hook : hooks) hook.addingContact(txn, contact); return c; } @@ -243,6 +247,25 @@ class ContactManagerImpl implements ContactManager, EventListener { db.transaction(false, txn -> setContactAlias(txn, c, alias)); } + @Override + public void setHandshakePublicKey(Transaction txn, ContactId c, + PublicKey handshakePublicKey) throws DbException, GeneralSecurityException { + if (handshakePublicKey.getKeyType() != + CryptoConstants.KEY_TYPE_AGREEMENT) { + throw new IllegalArgumentException(); + } + db.setHandshakePublicKey(txn, c, handshakePublicKey); + KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn); + keyManager.addContact(txn, c, handshakePublicKey, ourKeyPair); + } + + @Override + public void setHandshakePublicKey(ContactId c, PublicKey handshakePublicKey) + throws DbException, GeneralSecurityException { + db.transaction(false, + txn -> setHandshakePublicKey(txn, c, handshakePublicKey)); + } + @Override public boolean contactExists(Transaction txn, AuthorId remoteAuthorId, AuthorId localAuthorId) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java index f6745af24..bd7568dc1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java @@ -32,6 +32,7 @@ import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeys; +import java.sql.Connection; import java.util.Collection; import java.util.List; import java.util.Map; @@ -695,6 +696,11 @@ interface Database { void setHandshakeKeyPair(T txn, AuthorId local, PublicKey publicKey, PrivateKey privateKey) throws DbException; + /** + * Sets the handshake public key for a given contact + */ + void setHandshakePublicKey(T txn, ContactId c, PublicKey handshakePublicKey) throws DbException; + /** * Marks the given message as permanent, i.e. not temporary. */ diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index 6ec1760af..245efde96 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -1040,6 +1040,15 @@ class DatabaseComponentImpl implements DatabaseComponent { } } + @Override + public void setHandshakePublicKey(Transaction transaction, ContactId c, PublicKey handshakePublicKey) throws DbException { + if (transaction.isReadOnly()) throw new IllegalArgumentException(); + T txn = unbox(transaction); + if (!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.setHandshakePublicKey(txn, c, handshakePublicKey); + } + @Override public void setHandshakeKeyPair(Transaction transaction, AuthorId local, PublicKey publicKey, PrivateKey privateKey) throws DbException { diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java index c56b48e8d..6b2471952 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java @@ -3058,6 +3058,23 @@ abstract class JdbcDatabase implements Database { } } + @Override + public void setHandshakePublicKey(Connection txn, ContactId c, PublicKey handshakePublicKey) throws DbException { + PreparedStatement ps = null; + try { + String sql = "UPDATE contacts SET handshakePublicKey = ? WHERE contactId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, handshakePublicKey.getEncoded()); + ps.setInt(2, c.getInt()); + int affected = ps.executeUpdate(); + if (affected < 0 || affected > 1) throw new DbStateException(); + ps.close(); + } catch (SQLException e) { + tryToClose(ps, LOG, WARNING); + throw new DbException(e); + } + } + @Override public void setGroupVisibility(Connection txn, ContactId c, GroupId g, boolean shared) throws DbException { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index c43c91b7a..ecf76449b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -387,6 +387,13 @@ public class ConversationActivity extends BriarActivity } }); + // enable help recover account action if available + observeOnce(viewModel.amCustodian(), this, enable -> { + if (enable) { + menu.findItem(R.id.action_help_recover_account).setEnabled(true); + } + }); + return super.onCreateOptionsMenu(menu); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java index 398799980..a6eeda0bf 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java @@ -41,6 +41,7 @@ import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent; import org.briarproject.briar.api.remotewipe.RemoteWipeManager; +import org.briarproject.briar.api.socialbackup.SocialBackupManager; import java.util.Collection; import java.util.List; @@ -86,6 +87,7 @@ public class ConversationViewModel extends DbViewModel private final AttachmentRetriever attachmentRetriever; private final AttachmentCreator attachmentCreator; private final RemoteWipeManager remoteWipeManager; + private final SocialBackupManager socialBackupManager; @Nullable private ContactId contactId = null; @@ -108,6 +110,7 @@ public class ConversationViewModel extends DbViewModel new MutableLiveEvent<>(); private final MutableLiveData amRemoteWiper = new MutableLiveData<>(); private final MutableLiveData isRemoteWiper = new MutableLiveData<>(); + private final MutableLiveData amCustodian = new MutableLiveData<>(); @Inject ConversationViewModel(Application application, @@ -123,7 +126,8 @@ public class ConversationViewModel extends DbViewModel PrivateMessageFactory privateMessageFactory, AttachmentRetriever attachmentRetriever, RemoteWipeManager remoteWipeManager, - AttachmentCreator attachmentCreator) { + AttachmentCreator attachmentCreator, + SocialBackupManager socialBackupManager) { super(application, dbExecutor, lifecycleManager, db, androidExecutor); this.db = db; this.eventBus = eventBus; @@ -135,6 +139,7 @@ public class ConversationViewModel extends DbViewModel this.attachmentRetriever = attachmentRetriever; this.attachmentCreator = attachmentCreator; this.remoteWipeManager = remoteWipeManager; + this.socialBackupManager = socialBackupManager; messagingGroupId = map(contactItem, c -> messagingManager.getContactGroup(c.getContact()).getId()); contactDeleted.setValue(false); @@ -311,6 +316,10 @@ public class ConversationViewModel extends DbViewModel boolean isWiper = db.transactionWithResult(true, txn -> remoteWipeManager.isWiper(txn, c)); isRemoteWiper.postValue(isWiper); + // Check if we are a social backup custodian for this contact + boolean amCustodianBool = db.transactionWithResult(true, + txn -> socialBackupManager.amCustodian(txn, c)); + amCustodian.postValue(amCustodianBool); } @DatabaseExecutor @@ -404,6 +413,10 @@ public class ConversationViewModel extends DbViewModel return isRemoteWiper; } + LiveData amCustodian() { + return amCustodian; + } + @UiThread void recheckFeaturesAndOnboarding(ContactId contactId) { runOnDbThread(() -> { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DistributedBackupActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DistributedBackupActivity.java index 593670034..aa9f48275 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DistributedBackupActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/DistributedBackupActivity.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.widget.Toast; import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; import org.briarproject.briar.R; @@ -29,6 +30,9 @@ public class DistributedBackupActivity extends BriarActivity implements @Inject public SocialBackupManager socialBackupManager; + @Inject + public ContactManager contactManager; + @Inject public DatabaseComponent db; @@ -52,6 +56,20 @@ public class DistributedBackupActivity extends BriarActivity implements showInitialFragment(fragment); }); } catch (DbException e) { + // Check the number of contacts in the contacts list > 1 + try { + if (contactManager.getContacts().size() < 2) { + Toast.makeText(this, + R.string.social_backup_not_enough_contacts, + Toast.LENGTH_LONG).show(); + finish(); + } + } catch (DbException dbException) { + Toast.makeText(this, + R.string.reading_contacts_error, + Toast.LENGTH_LONG).show(); + finish(); + } CustodianSelectorFragment fragment = CustodianSelectorFragment.newInstance(); showInitialFragment(fragment); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ThresholdSelectorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ThresholdSelectorFragment.java index c20a64dc4..c4b689f39 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ThresholdSelectorFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/ThresholdSelectorFragment.java @@ -64,17 +64,13 @@ public class ThresholdSelectorFragment extends BaseFragment { message = view.findViewById(R.id.textViewMessage); mOfn = view.findViewById(R.id.textViewmOfn); - if (numberOfCustodians == 2) { - message.setText(R.string.threshold_too_few_custodians); - } if (numberOfCustodians > 3) { seekBar.setMax(numberOfCustodians -3); - seekBar.setProgress(threshold - 2); seekBar.setOnSeekBarChangeListener(new SeekBarListener()); recommendedThreshold = SecretSharingWrapper.defaultThreshold(numberOfCustodians); threshold = recommendedThreshold; - + seekBar.setProgress(threshold - 2); } else { seekBar.setEnabled(false); threshold = 2; 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 f6ce8f27b..d7f1db7bc 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 @@ -1,6 +1,8 @@ package org.briarproject.briar.android.socialbackup.recover; import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; import android.graphics.Bitmap; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; @@ -14,6 +16,7 @@ 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.MessageEncoder; import org.briarproject.briar.api.socialbackup.ReturnShardPayload; import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount; import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask; @@ -22,6 +25,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.security.GeneralSecurityException; +import java.util.HashSet; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -50,6 +54,7 @@ class OwnerReturnShardViewModel extends AndroidViewModel private final Executor ioExecutor; private final SecretOwnerTask task; private final RestoreAccount restoreAccount; + private final SharedPreferences prefs; private final MutableLiveEvent errorTryAgain = new MutableLiveEvent<>(); @@ -65,18 +70,26 @@ class OwnerReturnShardViewModel extends AndroidViewModel private Bitmap qrCodeBitmap; private WifiManager wifiManager; private SecretKey secretKey; + private final MessageEncoder messageEncoder; @Inject OwnerReturnShardViewModel(Application app, AndroidExecutor androidExecutor, SecretOwnerTask task, RestoreAccount restoreAccount, - @IoExecutor Executor ioExecutor) { + @IoExecutor Executor ioExecutor, + MessageEncoder messageEncoder) { super(app); this.androidExecutor = androidExecutor; this.ioExecutor = ioExecutor; this.restoreAccount = restoreAccount; + this.messageEncoder = messageEncoder; this.task = task; + this.prefs = app.getSharedPreferences("account-recovery", + Context.MODE_PRIVATE); + restoreAccount.restoreFromPrevious(prefs.getStringSet("recovered", new HashSet<>())); + + wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE); // IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED); @@ -144,13 +157,6 @@ class OwnerReturnShardViewModel extends AndroidViewModel ioExecutor.execute(() -> { task.start(this, getWifiIpv4Address()); }); -// KeyAgreementTask oldTask = task; -// KeyAgreementTask newTask = keyAgreementTaskProvider.get(); -// task = newTask; -// ioExecutor.execute(() -> { -// if (oldTask != null) oldTask.stopListening(); -// newTask.listen(); -// }); } @UiThread @@ -218,16 +224,18 @@ class OwnerReturnShardViewModel extends AndroidViewModel this.state.postValue(state); }); } else if (state instanceof SecretOwnerTask.State.Success) { -// startClicked.setEvent(true); this.state.postValue(state); - // TODO do same for failure } else { this.state.postValue(state); } } public RestoreAccount.AddReturnShardPayloadResult addToShardSet(ReturnShardPayload toAdd) { - return restoreAccount.addReturnShardPayload(toAdd); + RestoreAccount.AddReturnShardPayloadResult result = restoreAccount.addReturnShardPayload(toAdd); + if (result == RestoreAccount.AddReturnShardPayloadResult.OK) { + prefs.edit().putStringSet("recovered", restoreAccount.getEncodedShards()).apply(); + } + return result; } public boolean canRecover() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountActivity.java index 2c9a3fefe..b8a97e7b8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountActivity.java @@ -52,9 +52,11 @@ public class RestoreAccountActivity extends BaseActivity showInitialFragment(RestoreAccountSetPasswordFragment.newInstance()); } else if (state == State.DOZE) { showDozeFragment(); - } else if (state == State.CREATED || state == State.FAILED) { - // TODO: Show an error if failed + } else if (state == State.CREATED) { showApp(); + } else { // FAILED + // TODO: Show an error if failed + finish(); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountViewModel.java index beeaf22f4..a11ac2353 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/socialbackup/recover/RestoreAccountViewModel.java @@ -1,6 +1,8 @@ package org.briarproject.briar.android.socialbackup.recover; import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.contact.ContactManager; @@ -45,27 +47,27 @@ class RestoreAccountViewModel extends AndroidViewModel { new MutableLiveData<>(false); private final AccountManager accountManager; - private final ContactManager contactManager; private final Executor ioExecutor; private final PasswordStrengthEstimator strengthEstimator; private final DozeHelper dozeHelper; private final RestoreAccount restoreAccount; + private final SharedPreferences prefs; @Inject RestoreAccountViewModel(Application app, AccountManager accountManager, - ContactManager contactManager, RestoreAccount restoreAccount, @IoExecutor Executor ioExecutor, PasswordStrengthEstimator strengthEstimator, DozeHelper dozeHelper) { super(app); this.accountManager = accountManager; - this.contactManager = contactManager; this.ioExecutor = ioExecutor; this.strengthEstimator = strengthEstimator; this.dozeHelper = dozeHelper; this.restoreAccount = restoreAccount; + this.prefs = app.getSharedPreferences("account-recovery", + Context.MODE_PRIVATE); ioExecutor.execute(() -> { if (accountManager.accountExists()) { @@ -112,17 +114,24 @@ class RestoreAccountViewModel extends AndroidViewModel { if (socialBackup == null) { LOG.warning("Cannot retrieve social backup"); state.postEvent(State.FAILED); + return; } Identity identity = socialBackup.getIdentity(); ioExecutor.execute(() -> { if (accountManager.restoreAccount(identity, password)) { LOG.info("Restored account"); try { - restoreAccount.addContactsToDb(); + restoreAccount.restoreAccountWhenDatabaseReady(); } catch (DbException e) { - LOG.warning("Cannot retrieve social backup"); + LOG.warning("Failure processing social backup"); + e.printStackTrace(); state.postEvent(State.FAILED); + return; } + + // Remove partial recovery from shared preferences + prefs.edit().clear().apply(); + state.postEvent(State.CREATED); } else { LOG.warning("Failed to create account"); diff --git a/briar-android/src/main/res/menu/conversation_actions.xml b/briar-android/src/main/res/menu/conversation_actions.xml index 5c699cac1..89f6b203d 100644 --- a/briar-android/src/main/res/menu/conversation_actions.xml +++ b/briar-android/src/main/res/menu/conversation_actions.xml @@ -31,6 +31,7 @@ android:id="@+id/action_help_recover_account" android:icon="@drawable/introduction_white" android:title="@string/help_recover_account" + android:enabled="false" app:showAsAction="never"/> Your nickname will be shown next to any content you post. You can\'t change it after creating your account. Next Choose a Password - Your Briar account is stored encrypted on your device, not in the cloud. If you forget your password or uninstall Briar, there\'s no way to recover your account.\n\nChoose a long password that\'s hard to guess, such as four random words, or ten random letters, numbers and symbols. + Your Briar account is stored encrypted on your device, not in the cloud. If you forget your password or uninstall Briar, you can only recover your account if you have made a social backup.\n\nChoose a long password that\'s hard to guess, such as four random words, or ten random letters, numbers and symbols. Background Connections To receive messages, Briar needs to stay connected in the background. To receive messages, Briar needs to stay connected in the background. Please disable battery optimizations so Briar can stay connected. @@ -37,7 +37,7 @@ Sign In I have forgotten my password Lost Password - Your Briar account is stored encrypted on your device, not in the cloud, so we can\'t reset your password. Would you like to delete your account and start again?\n\nCaution: Your identities, contacts and messages will be permanently lost. + Your Briar account is stored encrypted on your device, not in the cloud, so we can\'t reset your password. If you have made a social backup, you can delete your account and set a new password when you restore it. Would you like to delete your account and start again?\n\nCaution: Your identities, contacts and messages will be permanently lost if you do not have a backup. Briar could not start Tap for more information. Briar Startup Failure @@ -664,7 +664,6 @@ Secure - recommended threshold Insecure – higher threshold recommended Danger of loss – lower threshold recommended - Danger of loss - more contacts recommended Choose Threshold %d of %d contacts needed to recover your account @@ -730,6 +729,9 @@ Help recover account %d of the following contacts are needed to restore your account: + To make a social backup, you need at least 2 contacts in your contacts list + There was an error reading your contacts list + diff --git a/briar-api/src/main/java/org/briarproject/briar/api/handshakekeyexchange/HandshakeKeyExchangeManager.java b/briar-api/src/main/java/org/briarproject/briar/api/handshakekeyexchange/HandshakeKeyExchangeManager.java new file mode 100644 index 000000000..c6da01697 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/handshakekeyexchange/HandshakeKeyExchangeManager.java @@ -0,0 +1,22 @@ +package org.briarproject.briar.api.handshakekeyexchange; + +import org.briarproject.bramble.api.sync.ClientId; +import org.briarproject.briar.api.conversation.ConversationManager; + +public interface HandshakeKeyExchangeManager extends ConversationManager.ConversationClient { + + /** + * The unique ID of the client. + */ + ClientId CLIENT_ID = new ClientId("org.briarproject.briar.handshakekeyexchange"); + + /** + * The current major version of the handshake key exchange client. + */ + int MAJOR_VERSION = 0; + + /** + * The current minor version of the handshake key exchange client. + */ + int MINOR_VERSION = 0; +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackup.java b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackup.java index 1be5a0822..86ea4847b 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackup.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackup.java @@ -1,17 +1,22 @@ package org.briarproject.briar.api.socialbackup; import org.briarproject.bramble.api.identity.Identity; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; import java.util.List; +import java.util.Map; public class SocialBackup { private Identity identity; private List contacts; + private Map localTransportProperties; private int version; - public SocialBackup (Identity identity, List contacts, int version) { + public SocialBackup (Identity identity, List contacts, Map localTransportProperties, int version) { this.identity = identity; this.contacts = contacts; + this.localTransportProperties = localTransportProperties; this.version = version; } @@ -23,6 +28,10 @@ public class SocialBackup { return contacts; } + public Map getLocalTransportProperties() { + return localTransportProperties; + } + public int getVersion() { return version; } diff --git a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/recovery/RestoreAccount.java b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/recovery/RestoreAccount.java index cd03442c9..2edc812b7 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/recovery/RestoreAccount.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/recovery/RestoreAccount.java @@ -6,17 +6,21 @@ import org.briarproject.briar.api.socialbackup.ReturnShardPayload; import org.briarproject.briar.api.socialbackup.SocialBackup; import java.security.GeneralSecurityException; +import java.util.Set; public interface RestoreAccount { enum AddReturnShardPayloadResult { DUPLICATE, MISMATCH, - OK + OK, + RECOVERED } int getNumberOfShards(); + Set getEncodedShards(); + AddReturnShardPayloadResult addReturnShardPayload(ReturnShardPayload toAdd); boolean canRecover(); @@ -25,5 +29,11 @@ public interface RestoreAccount { SocialBackup getSocialBackup(); - void addContactsToDb() throws DbException; +// void addContactsToDb() throws DbException; + + void restoreFromPrevious(Set previousShards); + + void restoreAccountWhenDatabaseReady() throws DbException; + +// void addLocalTransportProperties() throws DbException; } diff --git a/briar-core/src/main/java/org/briarproject/briar/BriarCoreEagerSingletons.java b/briar-core/src/main/java/org/briarproject/briar/BriarCoreEagerSingletons.java index 519d2b6c0..66e73a154 100644 --- a/briar-core/src/main/java/org/briarproject/briar/BriarCoreEagerSingletons.java +++ b/briar-core/src/main/java/org/briarproject/briar/BriarCoreEagerSingletons.java @@ -4,6 +4,7 @@ import org.briarproject.briar.avatar.AvatarModule; import org.briarproject.briar.blog.BlogModule; import org.briarproject.briar.feed.FeedModule; import org.briarproject.briar.forum.ForumModule; +import org.briarproject.briar.handshakekeyexchange.HandshakeKeyExchangeModule; import org.briarproject.briar.identity.IdentityModule; import org.briarproject.briar.introduction.IntroductionModule; import org.briarproject.briar.messaging.MessagingModule; @@ -12,7 +13,6 @@ import org.briarproject.briar.privategroup.invitation.GroupInvitationModule; import org.briarproject.briar.remotewipe.RemoteWipeModule; import org.briarproject.briar.sharing.SharingModule; import org.briarproject.briar.socialbackup.SocialBackupModule; -//import org.briarproject.briar.socialbackup.DefaultSocialBackupModule; public interface BriarCoreEagerSingletons { @@ -40,6 +40,8 @@ public interface BriarCoreEagerSingletons { void inject(RemoteWipeModule.EagerSingletons init); + void inject(HandshakeKeyExchangeModule.EagerSingletons init); + class Helper { public static void injectEagerSingletons(BriarCoreEagerSingletons c) { @@ -55,6 +57,7 @@ public interface BriarCoreEagerSingletons { c.inject(new IntroductionModule.EagerSingletons()); c.inject(new SocialBackupModule.EagerSingletons()); c.inject(new RemoteWipeModule.EagerSingletons()); + c.inject(new HandshakeKeyExchangeModule.EagerSingletons()); } } } diff --git a/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java b/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java index afab0a7fc..4b8a04479 100644 --- a/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java @@ -7,6 +7,7 @@ import org.briarproject.briar.client.BriarClientModule; import org.briarproject.briar.feed.DnsModule; import org.briarproject.briar.feed.FeedModule; import org.briarproject.briar.forum.ForumModule; +import org.briarproject.briar.handshakekeyexchange.HandshakeKeyExchangeModule; import org.briarproject.briar.identity.IdentityModule; import org.briarproject.briar.introduction.IntroductionModule; import org.briarproject.briar.messaging.MessagingModule; @@ -35,6 +36,7 @@ import dagger.Module; RemoteWipeModule.class, SharingModule.class, SocialBackupModule.class, + HandshakeKeyExchangeModule.class, TestModule.class }) public class BriarCoreModule { diff --git a/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeConstants.java b/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeConstants.java new file mode 100644 index 000000000..8b4aa7ed4 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeConstants.java @@ -0,0 +1,13 @@ +package org.briarproject.briar.handshakekeyexchange; + +public interface HandshakeKeyExchangeConstants { + + // Group metadata keys + String GROUP_KEY_CONTACT_ID = "contactId"; + + // Message metadata keys + String MSG_KEY_TIMESTAMP = "timestamp"; + String MSG_KEY_MESSAGE_TYPE = "messageType"; + String MSG_KEY_LOCAL = "local"; + String MSG_KEY_VERSION = "version"; +} diff --git a/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeManagerImpl.java new file mode 100644 index 000000000..5a6d9010c --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeManagerImpl.java @@ -0,0 +1,260 @@ +package org.briarproject.briar.handshakekeyexchange; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.crypto.AgreementPublicKey; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataParser; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.briar.api.client.MessageTracker; +import org.briarproject.briar.api.conversation.ConversationMessageHeader; +import org.briarproject.briar.api.conversation.DeletionResult; +import org.briarproject.briar.api.handshakekeyexchange.HandshakeKeyExchangeManager; +import org.briarproject.briar.client.ConversationClientImpl; + +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Logger.getLogger; +import static org.briarproject.briar.handshakekeyexchange.HandshakeKeyExchangeConstants.GROUP_KEY_CONTACT_ID; +import static org.briarproject.briar.handshakekeyexchange.HandshakeKeyExchangeConstants.MSG_KEY_LOCAL; +import static org.briarproject.briar.handshakekeyexchange.HandshakeKeyExchangeConstants.MSG_KEY_TIMESTAMP; + +public class HandshakeKeyExchangeManagerImpl extends ConversationClientImpl + implements + HandshakeKeyExchangeManager, LifecycleManager.OpenDatabaseHook, + ContactManager.ContactHook, + ClientVersioningManager.ClientVersioningHook { + + private final ClientVersioningManager clientVersioningManager; + private final ContactGroupFactory contactGroupFactory; + private final ContactManager contactManager; + private final IdentityManager identityManager; + private final Group localGroup; + private final Clock clock; + private PublicKey handshakePublicKey; + private static final Logger LOG = + getLogger(HandshakeKeyExchangeManager.class.getName()); + + + @Inject + protected HandshakeKeyExchangeManagerImpl( + DatabaseComponent db, + ClientHelper clientHelper, + MetadataParser metadataParser, + MessageTracker messageTracker, + ClientVersioningManager clientVersioningManager, + ContactGroupFactory contactGroupFactory, + ContactManager contactManager, + IdentityManager identityManager, + Clock clock + ) { + super(db, clientHelper, metadataParser, messageTracker); + this.clientVersioningManager = clientVersioningManager; + this.contactGroupFactory = contactGroupFactory; + this.contactManager = contactManager; + this.identityManager = identityManager; + this.clock = clock; + localGroup = + contactGroupFactory.createLocalGroup(CLIENT_ID, MAJOR_VERSION); + } + + @Override + public void onDatabaseOpened(Transaction txn) throws DbException { + // Get our own handshake public key + handshakePublicKey = identityManager.getHandshakeKeys(txn).getPublic(); + + if (!db.containsGroup(txn, localGroup.getId())) { + db.addGroup(txn, localGroup); + + // Set things up for any pre-existing contacts + for (Contact c : db.getContacts(txn)) addingContact(txn, c); + } else { + for (Contact c : db.getContacts(txn)) { + if (c.getHandshakePublicKey() == null) { + sendHandshakePublicKey(txn, c); + } else { + LOG.info("Have pk for contact " + c.getAuthor().getName()); + } + } + } + } + + @Override + public Group getContactGroup(Contact c) { + return contactGroupFactory.createContactGroup(CLIENT_ID, + MAJOR_VERSION, c); + } + + @Override + public Collection getMessageHeaders( + Transaction txn, ContactId contactId) throws DbException { + return new ArrayList<>(); + } + + @Override + public Set getMessageIds(Transaction txn, ContactId contactId) + throws DbException { + Contact contact = db.getContact(txn, contactId); + GroupId contactGroupId = getContactGroup(contact).getId(); + try { + Map messages = clientHelper + .getMessageMetadataAsDictionary(txn, contactGroupId); + return messages.keySet(); + } catch (FormatException e) { + throw new DbException(e); + } + } + + @Override + public DeletionResult deleteAllMessages(Transaction txn, ContactId c) + throws DbException { + GroupId g = getContactGroup(db.getContact(txn, c)).getId(); + for (MessageId messageId : db.getMessageIds(txn, g)) { + db.deleteMessage(txn, messageId); + db.deleteMessageMetadata(txn, messageId); + } + messageTracker.initializeGroupCount(txn, g); + return new DeletionResult(); + } + + + @Override + public DeletionResult deleteMessages(Transaction txn, ContactId c, + Set messageIds) throws DbException { + for (MessageId m : messageIds) { + db.deleteMessage(txn, m); + db.deleteMessageMetadata(txn, m); + } + return new DeletionResult(); + } + + @Override + protected boolean incomingMessage(Transaction txn, Message m, BdfList body, + BdfDictionary meta) throws DbException, FormatException { + LOG.info("Incoming HandshakeKeyExchange message"); + ContactId contactId = getContactId(txn, m.getGroupId()); + Contact c = contactManager.getContact(txn, contactId); + if (c.getHandshakePublicKey() != null) { + LOG.info("Already have public key - ignoring message"); + return false; + } + LOG.info("Adding contact's handshake public key"); + PublicKey handshakePublicKey = new AgreementPublicKey(body.getRaw(0)); + + try { + contactManager + .setHandshakePublicKey(txn, contactId, handshakePublicKey); + } catch (GeneralSecurityException e) { + LOG.warning("Security exception when adding remote handshake public key"); + e.printStackTrace(); + } + return false; + } + + private ContactId getContactId(Transaction txn, GroupId g) + throws DbException { + try { + BdfDictionary meta = + clientHelper.getGroupMetadataAsDictionary(txn, g); + return new ContactId(meta.getLong( + HandshakeKeyExchangeConstants.GROUP_KEY_CONTACT_ID) + .intValue()); + } catch (FormatException e) { + throw new DbException(e); + } + } + + @Override + public void addingContact(Transaction txn, Contact c) throws DbException { + // Create a group to share with the contact + Group g = getContactGroup(c); + db.addGroup(txn, g); + // Apply the client's visibility to the contact group + Group.Visibility client = + clientVersioningManager.getClientVisibility(txn, + c.getId(), CLIENT_ID, MAJOR_VERSION); + db.setGroupVisibility(txn, c.getId(), g.getId(), client); + // Attach the contact ID to the group + setContactId(txn, g.getId(), c.getId()); + + if (c.getHandshakePublicKey() == null) { + sendHandshakePublicKey(txn, c); + } else { + LOG.info("Have pk for contact " + c.getAuthor().getName()); + } + } + + private void sendHandshakePublicKey(Transaction txn, Contact c) + throws DbException { + LOG.info("Sending our handshake public key to " + c.getAuthor().getName()); + Group group = getContactGroup(c); + GroupId g = group.getId(); + if (!db.containsGroup(txn, g)) db.addGroup(txn, group); + long timestamp = clock.currentTimeMillis(); + + BdfList bodyList = new BdfList(); + bodyList.add(handshakePublicKey); + try { + byte[] body = clientHelper.toByteArray(bodyList); + Message m = clientHelper.createMessage(g, timestamp, body); + + BdfDictionary meta = BdfDictionary.of( + new BdfEntry(MSG_KEY_LOCAL, true), + new BdfEntry(MSG_KEY_TIMESTAMP, timestamp) + ); + clientHelper.addLocalMessage(txn, m, meta, true, false); + } catch (FormatException e) { + throw new DbException(); + } + +// messageTracker.trackOutgoingMessage(txn, m); + } + + @Override + public void removingContact(Transaction txn, Contact c) throws DbException { + db.removeGroup(txn, getContactGroup(c)); + } + + private void setContactId(Transaction txn, GroupId g, ContactId c) + throws DbException { + BdfDictionary d = new BdfDictionary(); + d.put(GROUP_KEY_CONTACT_ID, c.getInt()); + try { + clientHelper.mergeGroupMetadata(txn, g, d); + } catch (FormatException e) { + throw new AssertionError(e); + } + } + + @Override + public void onClientVisibilityChanging(Transaction txn, Contact c, + Group.Visibility v) throws DbException { + // Apply the client's visibility to the contact group + Group g = getContactGroup(c); + db.setGroupVisibility(txn, c.getId(), g.getId(), v); + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeModule.java b/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeModule.java new file mode 100644 index 000000000..e1cb335f9 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeModule.java @@ -0,0 +1,64 @@ +package org.briarproject.briar.handshakekeyexchange; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.sync.validation.ValidationManager; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.briar.api.conversation.ConversationManager; +import org.briarproject.briar.api.handshakekeyexchange.HandshakeKeyExchangeManager; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class HandshakeKeyExchangeModule { + public static class EagerSingletons { + @Inject + HandshakeKeyExchangeManager handshakeKeyExchangeManager; + + @Inject + HandshakeKeyExchangeValidator handshakeKeyExchangeValidator; + } + + @Provides + @Singleton + HandshakeKeyExchangeManager handshakeKeyExchangeManager( + LifecycleManager lifecycleManager, + ValidationManager validationManager, + ContactManager contactManager, + ClientVersioningManager clientVersioningManager, + ConversationManager conversationManager, + HandshakeKeyExchangeManagerImpl handshakeKeyExchangeManager) { + + lifecycleManager.registerOpenDatabaseHook(handshakeKeyExchangeManager); + validationManager + .registerIncomingMessageHook(HandshakeKeyExchangeManager.CLIENT_ID, + HandshakeKeyExchangeManager.MAJOR_VERSION, handshakeKeyExchangeManager); + + contactManager.registerContactHook(handshakeKeyExchangeManager); + clientVersioningManager.registerClient(HandshakeKeyExchangeManager.CLIENT_ID, HandshakeKeyExchangeManager.MAJOR_VERSION, + HandshakeKeyExchangeManager.MINOR_VERSION, handshakeKeyExchangeManager); + conversationManager.registerConversationClient(handshakeKeyExchangeManager); + return handshakeKeyExchangeManager; + } + + @Provides + @Singleton + HandshakeKeyExchangeValidator handshakeKeyExchangeValidator( + ValidationManager validationManager, + ClientHelper clientHelper, + MetadataEncoder metadataEncoder, + Clock clock) { + HandshakeKeyExchangeValidator validator = + new HandshakeKeyExchangeValidator(clientHelper, metadataEncoder, clock); + validationManager.registerMessageValidator(HandshakeKeyExchangeManager.CLIENT_ID, HandshakeKeyExchangeManager.MAJOR_VERSION, + validator); + return validator; + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeValidator.java b/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeValidator.java new file mode 100644 index 000000000..1a26694c7 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/handshakekeyexchange/HandshakeKeyExchangeValidator.java @@ -0,0 +1,40 @@ +package org.briarproject.briar.handshakekeyexchange; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.BdfMessageContext; +import org.briarproject.bramble.api.client.BdfMessageValidator; +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.data.MetadataEncoder; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.InvalidMessageException; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.system.Clock; +import static org.briarproject.briar.handshakekeyexchange.HandshakeKeyExchangeConstants.MSG_KEY_LOCAL; + +import static org.briarproject.bramble.util.ValidationUtils.checkSize; + +import javax.inject.Inject; + +public class HandshakeKeyExchangeValidator extends BdfMessageValidator { + + @Inject + protected HandshakeKeyExchangeValidator( + ClientHelper clientHelper, + MetadataEncoder metadataEncoder, + Clock clock) { + super(clientHelper, metadataEncoder, clock); + } + + @Override + protected BdfMessageContext validateMessage(Message m, Group g, + BdfList body) throws InvalidMessageException, FormatException { + checkSize(body,1); + BdfDictionary meta = BdfDictionary.of( + new BdfEntry(MSG_KEY_LOCAL, false) + ); + return new BdfMessageContext(meta); + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoderImpl.java index 4171d8154..20404372a 100644 --- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadDecoderImpl.java @@ -15,9 +15,11 @@ import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.briar.api.socialbackup.BackupPayload; +import org.briarproject.briar.api.socialbackup.ContactData; import org.briarproject.briar.api.socialbackup.MessageParser; import org.briarproject.briar.api.socialbackup.Shard; import org.briarproject.briar.api.socialbackup.SocialBackup; @@ -55,7 +57,7 @@ public class BackupPayloadDecoderImpl implements BackupPayloadDecoder { this.messageParser = messageParser; } - public org.briarproject.briar.api.socialbackup.SocialBackup decodeBackupPayload( + public SocialBackup decodeBackupPayload( SecretKey secret, BackupPayload backupPayload) throws FormatException, GeneralSecurityException { @@ -98,13 +100,17 @@ public class BackupPayloadDecoderImpl implements BackupPayloadDecoder { PrivateKey handShakePrivateKey = new AgreementPrivateKey(bdfIdentity.getRaw(3)); + Map localProperties = clientHelper + .parseAndValidateTransportPropertiesMap( + bdfIdentity.getDictionary(4)); + LOG.info("Local transport properties parsed"); + Long created = System.currentTimeMillis(); Identity identity = new Identity(localAuthor, handshakePublicKey, handShakePrivateKey, created); - LOG.info("New identity created"); - List contactDataList = new ArrayList(); + List contactDataList = new ArrayList(); for (int i = 0; i < bdfContactData.size(); i++) { BdfList bdfData = bdfContactData.getList(i); @@ -143,9 +149,9 @@ public class BackupPayloadDecoderImpl implements BackupPayloadDecoder { org.briarproject.briar.api.socialbackup.ContactData contactData = new org.briarproject.briar.api.socialbackup.ContactData(contact, properties, shard); contactDataList.add(contactData); - LOG.info("Contact added"); + LOG.info("Contact fully parsed"); } - LOG.info("All contacts added"); - return new SocialBackup(identity, contactDataList, version); + LOG.info("All contacts fully parsed"); + return new SocialBackup(identity, contactDataList, localProperties, version); } } diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java index ed8e852b2..ca5274bd6 100644 --- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java @@ -3,14 +3,17 @@ package org.briarproject.briar.socialbackup; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.briar.api.socialbackup.BackupPayload; import org.briarproject.briar.api.socialbackup.ContactData; import java.util.List; +import java.util.Map; @NotNullByDefault interface BackupPayloadEncoder { BackupPayload encodeBackupPayload(SecretKey secret, Identity identity, - List contactData, int version); + List contactData, int version, Map localTransportProperties); } diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java index 7e867f697..ba1df879f 100644 --- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java @@ -10,11 +10,15 @@ import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.briar.api.socialbackup.BackupPayload; import org.briarproject.briar.api.socialbackup.Shard; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.List; +import java.util.Map; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; @@ -45,15 +49,26 @@ class BackupPayloadEncoderImpl implements BackupPayloadEncoder { } @Override - public org.briarproject.briar.api.socialbackup.BackupPayload encodeBackupPayload(SecretKey secret, - Identity identity, List contactData, int version) { + public BackupPayload encodeBackupPayload(SecretKey secret, + Identity identity, + List contactData, + int version, + Map localTransportProperties) { // Encode the local identity BdfList bdfIdentity = new BdfList(); LocalAuthor localAuthor = identity.getLocalAuthor(); bdfIdentity.add(clientHelper.toList(localAuthor)); bdfIdentity.add(localAuthor.getPrivateKey().getEncoded()); + + // Add handshake keypair + assert identity.getHandshakePublicKey() != null; bdfIdentity.add(identity.getHandshakePublicKey().getEncoded()); + assert identity.getHandshakePrivateKey() != null; bdfIdentity.add(identity.getHandshakePrivateKey().getEncoded()); + + // Add local transport properties + bdfIdentity.add(clientHelper.toDictionary(localTransportProperties)); + // Encode the contact data BdfList bdfContactData = new BdfList(); for (org.briarproject.briar.api.socialbackup.ContactData cd : contactData) { @@ -84,10 +99,13 @@ class BackupPayloadEncoderImpl implements BackupPayloadEncoder { int encrypted = cipher.process(plaintext, 0, plaintext.length, ciphertext, 0); if (encrypted != ciphertext.length) throw new AssertionError(); - byte[] ciphertextWithNonce = new byte[ciphertext.length + nonce.length]; + byte[] ciphertextWithNonce = + new byte[ciphertext.length + nonce.length]; System.arraycopy(nonce, 0, ciphertextWithNonce, 0, nonce.length); - System.arraycopy(ciphertext, 0, ciphertextWithNonce, nonce.length, ciphertext.length); - return new org.briarproject.briar.api.socialbackup.BackupPayload(ciphertextWithNonce); + System.arraycopy(ciphertext, 0, ciphertextWithNonce, nonce.length, + ciphertext.length); + return new org.briarproject.briar.api.socialbackup.BackupPayload( + ciphertextWithNonce); } catch (FormatException | GeneralSecurityException e) { throw new AssertionError(e); } diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java index 72c6e4a92..c4cfb09d1 100644 --- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java @@ -57,6 +57,7 @@ import org.briarproject.briar.client.ConversationClientImpl; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -66,7 +67,6 @@ import java.util.Set; import javax.annotation.Nullable; import javax.inject.Inject; -import static java.util.Collections.singletonMap; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; import static org.briarproject.briar.socialbackup.MessageType.BACKUP; @@ -440,14 +440,25 @@ class SocialBackupManagerImpl extends ConversationClientImpl int version) throws DbException { Identity identity = identityManager.getIdentity(txn); + + // Add local transport properties + Map localProps = + transportPropertyManager.getLocalProperties(txn); + Map filteredLocalProps = + new HashMap<>(); + if (localProps.get(TorConstants.ID) != null) { + filteredLocalProps + .put(TorConstants.ID, localProps.get(TorConstants.ID)); + } + return backupPayloadEncoder.encodeBackupPayload(secret, identity, - contactData, version); + contactData, version, filteredLocalProps); } private List loadContactData(Transaction txn) throws DbException { Collection contacts = contactManager.getContacts(txn); - List contactData = + List contactData = new ArrayList<>(); for (Contact c : contacts) { // Skip contacts that are in the process of being removed @@ -456,6 +467,10 @@ class SocialBackupManagerImpl extends ConversationClientImpl Map props = getTransportProperties(txn, c.getId()); Shard shard = getRemoteShard(txn, contactGroup.getId()); + if (c.getHandshakePublicKey() == null) { + System.out.println( + "Warning - adding contact with no handshake public key"); + } contactData .add(new org.briarproject.briar.api.socialbackup.ContactData( c, props, shard)); @@ -466,9 +481,14 @@ class SocialBackupManagerImpl extends ConversationClientImpl private Map getTransportProperties( Transaction txn, ContactId c) throws DbException { // TODO: Include filtered properties for other transports - TransportProperties p = transportPropertyManager - .getRemoteProperties(txn, c, TorConstants.ID); - return singletonMap(TorConstants.ID, p); + TransportId ids[] = {TorConstants.ID}; +// {TorConstants.ID, LanTcpConstants.ID, BluetoothConstants.ID}; + Map props = new HashMap(); + for (TransportId id : ids) { + props.put(id, transportPropertyManager + .getRemoteProperties(txn, c, id)); + } + return props; } private void sendShardMessage(Transaction txn, Contact custodian, @@ -562,7 +582,7 @@ class SocialBackupManagerImpl extends ConversationClientImpl db.deleteMessageMetadata(txn, prevId); } sendBackupMessage(txn, custodian, newVersion, payload); - } catch (NoSuchContactException|NoSuchGroupException e){ + } catch (NoSuchContactException | NoSuchGroupException e) { // The custodian is no longer a contact - continue } } diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/recovery/RestoreAccountImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/recovery/RestoreAccountImpl.java index 204622420..1e91ede33 100644 --- a/briar-core/src/main/java/org/briarproject/briar/socialbackup/recovery/RestoreAccountImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/recovery/RestoreAccountImpl.java @@ -1,7 +1,9 @@ package org.briarproject.briar.socialbackup.recovery; import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DatabaseComponent; @@ -9,9 +11,15 @@ import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.bramble.api.properties.TransportPropertyManager; +import org.briarproject.bramble.util.StringUtils; import org.briarproject.briar.api.socialbackup.BackupPayload; import org.briarproject.briar.api.socialbackup.ContactData; import org.briarproject.briar.api.socialbackup.DarkCrystal; +import org.briarproject.briar.api.socialbackup.MessageEncoder; +import org.briarproject.briar.api.socialbackup.MessageParser; import org.briarproject.briar.api.socialbackup.ReturnShardPayload; import org.briarproject.briar.api.socialbackup.Shard; import org.briarproject.briar.api.socialbackup.SocialBackup; @@ -21,6 +29,9 @@ import org.briarproject.briar.socialbackup.BackupPayloadDecoder; import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -29,14 +40,18 @@ import javax.inject.Inject; import static java.util.logging.Logger.getLogger; public class RestoreAccountImpl implements RestoreAccount { - private final ArrayList recoveredShards = new ArrayList<>(); + private final Set recoveredShards = new HashSet<>(); private final DarkCrystal darkCrystal; private final Executor ioExecutor; private final DatabaseComponent db; private final ContactManager contactManager; + private final MessageEncoder messageEncoder; + private final MessageParser messageParser; + private final TransportPropertyManager transportPropertyManager; private final LifecycleManager lifecycleManager; private SecretKey secretKey; private final BackupPayloadDecoder backupPayloadDecoder; + private final ClientHelper clientHelper; private SocialBackup socialBackup; private byte[] secretId; @@ -48,20 +63,29 @@ public class RestoreAccountImpl implements RestoreAccount { BackupPayloadDecoder backupPayloadDecoder, DatabaseComponent db, @IoExecutor Executor ioExecutor, ContactManager contactManager, - LifecycleManager lifecycleManager) { + LifecycleManager lifecycleManager, + TransportPropertyManager transportPropertyManager, + MessageEncoder messageEncoder, + MessageParser messageParser, + ClientHelper clientHelper) { this.darkCrystal = darkCrystal; this.backupPayloadDecoder = backupPayloadDecoder; this.db = db; this.ioExecutor = ioExecutor; this.lifecycleManager = lifecycleManager; this.contactManager = contactManager; + this.transportPropertyManager = transportPropertyManager; + this.messageEncoder = messageEncoder; + this.messageParser = messageParser; + this.clientHelper = clientHelper; } public int getNumberOfShards() { return recoveredShards.size(); } - public AddReturnShardPayloadResult addReturnShardPayload(ReturnShardPayload toAdd) { + public AddReturnShardPayloadResult addReturnShardPayload( + ReturnShardPayload toAdd) { AddReturnShardPayloadResult result = AddReturnShardPayloadResult.OK; // TODO figure out how to actually use a hash set for these objects for (ReturnShardPayload returnShardPayload : recoveredShards) { @@ -75,7 +99,8 @@ public class RestoreAccountImpl implements RestoreAccount { return AddReturnShardPayloadResult.MISMATCH; } recoveredShards.add(toAdd); - return AddReturnShardPayloadResult.OK; + return canRecover() ? AddReturnShardPayloadResult.RECOVERED : + AddReturnShardPayloadResult.OK; } public boolean canRecover() { @@ -115,10 +140,7 @@ public class RestoreAccountImpl implements RestoreAccount { return socialBackup; } - public void addContactsToDb() throws DbException { - if (socialBackup == null) throw new DbException(); - AuthorId localAuthorId = socialBackup.getIdentity().getId(); - + public void restoreAccountWhenDatabaseReady() throws DbException { ioExecutor.execute(() -> { try { lifecycleManager.waitForDatabase(); @@ -126,19 +148,80 @@ public class RestoreAccountImpl implements RestoreAccount { LOG.warning("Interrupted when waiting for database"); } try { - db.transaction(false, txn -> { - for (ContactData contactData : socialBackup.getContacts()) { - Contact c = contactData.getContact(); - LOG.info("Adding contact " + c.getAuthor().getName() + " " + c.getAlias()); - contactManager.addContact(txn, c.getAuthor(), localAuthorId, - c.getHandshakePublicKey(), c.isVerified()); - } - }); + addLocalTransportProperties(); + addContactsToDb(); } catch (DbException e) { - LOG.warning("Error adding contacts to database"); - LOG.warning(e.getMessage()); + LOG.warning("Error when processing backup"); + e.printStackTrace(); } - LOG.info("Added all contacts"); }); } + + + private void addContactsToDb() throws DbException { + if (socialBackup == null) throw new DbException(); + AuthorId localAuthorId = socialBackup.getIdentity().getId(); + + try { + db.transaction(false, txn -> { + for (ContactData contactData : socialBackup.getContacts()) { + Contact c = contactData.getContact(); + LOG.info("Adding contact " + c.getAuthor().getName() + + " " + c.getAlias()); + if (c.getHandshakePublicKey() == null) { + LOG.warning( + "Warning: contact has no handshake public key"); + } + ContactId contactId = contactManager + .addContact(txn, c.getAuthor(), localAuthorId, + c.getHandshakePublicKey(), + c.isVerified()); + transportPropertyManager + .addRemoteProperties(txn, contactId, + contactData.getProperties()); + } + }); + } catch (DbException e) { + LOG.warning("Error adding contacts to database"); + LOG.warning(e.getMessage()); + } catch (GeneralSecurityException e) { + LOG.warning("Error adding handshake key"); + LOG.warning(e.getMessage()); + } + LOG.info("Added all contacts"); + } + + private void addLocalTransportProperties() + throws DbException { + LOG.info("Adding local transport properties"); + for (Map.Entry propertiesEntry : socialBackup + .getLocalTransportProperties().entrySet()) { + LOG.info("Adding transport property " + + propertiesEntry.getKey().getString()); + transportPropertyManager + .mergeLocalProperties(propertiesEntry.getKey(), + propertiesEntry.getValue()); + } + } + + public Set getEncodedShards() { + Set s = new HashSet(); + for (ReturnShardPayload r : recoveredShards) { + s.add(StringUtils + .toHexString(messageEncoder.encodeReturnShardPayload(r))); + } + return s; + } + + public void restoreFromPrevious(Set previousShards) { + for (String s : previousShards) { + try { + addReturnShardPayload(messageParser.parseReturnShardPayload( + clientHelper.toList(StringUtils.fromHexString(s)))); + } catch (FormatException e) { + LOG.warning("Error parsing shard from previous session"); + e.printStackTrace(); + } + } + } } diff --git a/build.gradle b/build.gradle index 713884093..636c29f90 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,7 @@ buildscript { classpath files('libs/gradle-witness.jar') } +// ext.dagger_version = "2.33" ext.junit_version = "4.12" ext.jmock_version = '2.12.0' }