Merge branch 'social-backup-restore-activity' into 'social-backup-poc'

Social backup restore activity

See merge request briar/briar!1444
This commit is contained in:
peg
2021-04-27 08:10:18 +00:00
54 changed files with 1545 additions and 161 deletions

View File

@@ -31,15 +31,6 @@
<option name="PACKAGES_TO_USE_STAR_IMPORTS"> <option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value /> <value />
</option> </option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.account;
import org.briarproject.bramble.api.crypto.DecryptionException; import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -43,6 +44,17 @@ public interface AccountManager {
*/ */
boolean createAccount(String name, String password); boolean createAccount(String name, String password);
/**
* Restores a given identity by registering it with the
* {@link IdentityManager}. Creates a database key, encrypts it with the
* given password and stores it on disk. {@link #accountExists()} will
* return true after this method returns true.
* @param identity
* @param password
* @return
*/
boolean restoreAccount(Identity identity, String password);
/** /**
* Deletes all account state from disk. {@link #accountExists()} will * Deletes all account state from disk. {@link #accountExists()} will
* return false after this method returns. * return false after this method returns.

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException; import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -48,6 +49,9 @@ public interface ContactManager {
SecretKey rootKey, long timestamp, boolean alice, boolean verified, SecretKey rootKey, long timestamp, boolean alice, boolean verified,
boolean active) throws DbException; boolean active) throws DbException;
ContactId addContact(Transaction txn, Author remote, AuthorId local,
PublicKey handshake, boolean verified) throws DbException;
/** /**
* Stores a contact associated with the given local and remote pseudonyms, * Stores a contact associated with the given local and remote pseudonyms,
* replacing the given pending contact, derives and stores handshake mode * replacing the given pending contact, derives and stores handshake mode

View File

@@ -24,6 +24,19 @@ public interface HandshakeManager {
HandshakeResult handshake(PendingContactId p, InputStream in, HandshakeResult handshake(PendingContactId p, InputStream in,
StreamWriter out) throws DbException, IOException; StreamWriter out) throws DbException, IOException;
/**
* Handshakes with the given contact. Returns an ephemeral master key
* authenticated with both parties' handshake key pairs and a flag
* indicating whether the local peer is Alice or Bob.
*
* @param in An incoming stream for the handshake, which must be secured in
* handshake mode
* @param out An outgoing stream for the handshake, which must be secured
* in handshake mode
*/
HandshakeResult handshake(ContactId c, InputStream in, StreamWriter out)
throws DbException, IOException;
class HandshakeResult { class HandshakeResult {
private final SecretKey masterKey; private final SecretKey masterKey;

View File

@@ -35,6 +35,20 @@ public interface KeyManager {
ContactId c, SecretKey rootKey, long timestamp, boolean alice, ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException; boolean active) throws DbException;
/**
* Derives and stores a set of rotation mode transport keys for
* communicating with the given contact over each transport and returns the
* key set IDs.
* <p/>
* {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned.
*
* @param alice True if the local party is Alice
* @param active Whether the derived keys can be used for outgoing streams
*/
Map<TransportId, KeySetId> addRotationKeys(ContactId c, SecretKey rootKey,
long timestamp, boolean alice, boolean active) throws DbException;
/** /**
* Informs the key manager that a new contact has been added. Derives and * Informs the key manager that a new contact has been added. Derives and
* stores a set of handshake mode transport keys for communicating with the * stores a set of handshake mode transport keys for communicating with the

View File

@@ -176,6 +176,18 @@ class AccountManagerImpl implements AccountManager {
} }
} }
public boolean restoreAccount(Identity identity, String password) {
synchronized (stateChangeLock) {
if (hasDatabaseKey())
throw new AssertionError("Already have a database key");
identityManager.registerIdentity(identity);
SecretKey key = crypto.generateSecretKey();
if (!encryptAndStoreDatabaseKey(key, password)) return false;
databaseKey = key;
return true;
}
}
@GuardedBy("stateChangeLock") @GuardedBy("stateChangeLock")
private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) { private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) {
byte[] plaintext = key.getBytes(); byte[] plaintext = key.getBytes();

View File

@@ -76,7 +76,7 @@ class ConnectionManagerImpl implements ConnectionManager {
ioExecutor.execute(new IncomingDuplexSyncConnection(keyManager, ioExecutor.execute(new IncomingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory, connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor, syncSessionFactory, transportPropertyManager, ioExecutor,
t, d)); t, d, handshakeManager));
} }
@Override @Override
@@ -101,7 +101,7 @@ class ConnectionManagerImpl implements ConnectionManager {
ioExecutor.execute(new OutgoingDuplexSyncConnection(keyManager, ioExecutor.execute(new OutgoingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory, connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor, syncSessionFactory, transportPropertyManager, ioExecutor,
secureRandom, c, t, d)); secureRandom, handshakeManager, c, t, d));
} }
@Override @Override

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry; import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.connection.InterruptibleConnection; import org.briarproject.bramble.api.connection.InterruptibleConnection;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;

View File

@@ -2,6 +2,8 @@ package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry; import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId; 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.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
@@ -13,9 +15,11 @@ import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory; import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -24,6 +28,10 @@ import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
class IncomingDuplexSyncConnection extends DuplexSyncConnection class IncomingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable { implements Runnable {
private final HandshakeManager handshakeManager;
// FIXME: Exchange timestamp as part of handshake protocol?
private static final long TIMESTAMP = 1617235200; // 1 April 2021 00:00 UTC
IncomingDuplexSyncConnection(KeyManager keyManager, IncomingDuplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry, ConnectionRegistry connectionRegistry,
@@ -32,10 +40,12 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection
SyncSessionFactory syncSessionFactory, SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager,
Executor ioExecutor, TransportId transportId, Executor ioExecutor, TransportId transportId,
DuplexTransportConnection connection) { DuplexTransportConnection connection,
HandshakeManager handshakeManager) {
super(keyManager, connectionRegistry, streamReaderFactory, super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory, streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection); transportPropertyManager, ioExecutor, transportId, connection);
this.handshakeManager = handshakeManager;
} }
@Override @Override
@@ -54,10 +64,22 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection
return; return;
} }
if (ctx.isHandshakeMode()) { if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts if (!performHandshake(ctx, contactId)) {
LOG.warning("Received handshake tag, expected rotation mode"); LOG.warning("Handshake failed");
onReadError(true); return;
return; }
// Allocate a rotation mode stream context
ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
onWriteError();
return;
}
if (ctx.isHandshakeMode()) {
LOG.warning("Got handshake mode context after handshaking");
onWriteError();
return;
}
} }
connectionRegistry.registerIncomingConnection(contactId, transportId, connectionRegistry.registerIncomingConnection(contactId, transportId,
this); this);
@@ -103,5 +125,33 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection
onWriteError(); onWriteError();
} }
} }
private boolean performHandshake(StreamContext ctxIn, ContactId contactId) {
// Allocate the outgoing stream context
StreamContext ctxOut =
allocateStreamContext(contactId, transportId);
if (ctxOut == null) {
LOG.warning("Could not allocate stream context");
onReadError(true);
return false;
}
try {
InputStream in = streamReaderFactory.createStreamReader(
reader.getInputStream(), ctxIn);
// Flush the output stream to send the outgoing stream header
StreamWriter out = streamWriterFactory.createStreamWriter(
writer.getOutputStream(), ctxOut);
out.getOutputStream().flush();
HandshakeManager.HandshakeResult result =
handshakeManager.handshake(contactId, in, out);
keyManager.addRotationKeys(contactId, result.getMasterKey(),
TIMESTAMP, result.isAlice(), true);
return true;
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onReadError(true);
return false;
}
}
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry; import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
@@ -14,9 +15,11 @@ import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory; import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -28,21 +31,31 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class OutgoingDuplexSyncConnection extends DuplexSyncConnection class OutgoingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable { implements Runnable {
// FIXME: Exchange timestamp as part of handshake protocol?
private static final long TIMESTAMP = 1617235200; // 1 April 2021 00:00 UTC
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final HandshakeManager handshakeManager;
private final ContactId contactId; private final ContactId contactId;
OutgoingDuplexSyncConnection(KeyManager keyManager, OutgoingDuplexSyncConnection(
KeyManager keyManager,
ConnectionRegistry connectionRegistry, ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory, StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory, StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory, SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager,
Executor ioExecutor, SecureRandom secureRandom, ContactId contactId, Executor ioExecutor,
TransportId transportId, DuplexTransportConnection connection) { SecureRandom secureRandom,
HandshakeManager handshakeManager,
ContactId contactId,
TransportId transportId,
DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory, super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory, streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection); transportPropertyManager, ioExecutor, transportId, connection);
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.handshakeManager = handshakeManager;
this.contactId = contactId; this.contactId = contactId;
} }
@@ -56,10 +69,22 @@ class OutgoingDuplexSyncConnection extends DuplexSyncConnection
return; return;
} }
if (ctx.isHandshakeMode()) { if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts if (!performHandshake(ctx)) {
LOG.warning("Cannot use handshake mode stream context"); LOG.warning("Handshake failed");
onWriteError(); return;
return; }
// Allocate a rotation mode stream context
ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
onWriteError();
return;
}
if (ctx.isHandshakeMode()) {
LOG.warning("Got handshake mode context after handshaking");
onWriteError();
return;
}
} }
// Start the incoming session on another thread // Start the incoming session on another thread
Priority priority = generatePriority(); Priority priority = generatePriority();
@@ -127,6 +152,57 @@ class OutgoingDuplexSyncConnection extends DuplexSyncConnection
} }
} }
private boolean performHandshake(StreamContext ctxOut) {
// Flush the output stream to send the outgoing stream header
StreamWriter out;
try {
out = streamWriterFactory.createStreamWriter(
writer.getOutputStream(), ctxOut);
out.getOutputStream().flush();
} catch (IOException e) {
logException(LOG, WARNING, e);
onWriteError();
return false;
}
// Read and recognise the tag
StreamContext ctxIn = recogniseTag(reader, transportId);
// Unrecognised tags are suspicious in this case
if (ctxIn == null) {
LOG.warning("Unrecognised tag for returning stream");
onReadError();
return false;
}
// Check that the stream comes from the expected contact
ContactId inContactId = ctxIn.getContactId();
if (contactId == null) {
LOG.warning("Expected contact tag, got rendezvous tag");
onReadError();
return false;
}
if (!inContactId.equals(contactId)) {
LOG.warning("Wrong contact ID for returning stream");
onReadError();
return false;
}
// TODO: Register the connection, close it if it's redundant
// Handshake and exchange contacts
try {
InputStream in = streamReaderFactory.createStreamReader(
reader.getInputStream(), ctxIn);
HandshakeManager.HandshakeResult result =
handshakeManager.handshake(contactId, in, out);
keyManager.addRotationKeys(contactId, result.getMasterKey(),
TIMESTAMP, result.isAlice(), true);
return true;
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onWriteError();
return false;
} finally {
// TODO: Unregister the connection
}
}
private void onReadError() { private void onReadError() {
// 'Recognised' is always true for outgoing connections // 'Recognised' is always true for outgoing connections
onReadError(true); onReadError(true);

View File

@@ -119,6 +119,15 @@ class ContactManagerImpl implements ContactManager, EventListener {
verified, active)); verified, active));
} }
@Override
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
PublicKey handshake, boolean verified) throws DbException {
ContactId c = db.addContact(txn, remote, local, handshake, verified);
Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
return c;
}
@Override @Override
public String getHandshakeLink() throws DbException { public String getHandshakeLink() throws DbException {
KeyPair keyPair = db.transactionWithResult(true, KeyPair keyPair = db.transactionWithResult(true,

View File

@@ -3,6 +3,8 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.Predicate; import org.briarproject.bramble.api.Predicate;
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.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager; import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
@@ -88,6 +90,27 @@ class HandshakeManagerImpl implements HandshakeManager {
}); });
PublicKey theirStaticPublicKey = keys.getFirst(); PublicKey theirStaticPublicKey = keys.getFirst();
KeyPair ourStaticKeyPair = keys.getSecond(); KeyPair ourStaticKeyPair = keys.getSecond();
return handshake(theirStaticPublicKey, ourStaticKeyPair, in, out);
}
@Override
public HandshakeResult handshake(ContactId c, InputStream in,
StreamWriter out) throws DbException, IOException {
Pair<PublicKey, KeyPair> keys = db.transactionWithResult(true, txn -> {
Contact contact = contactManager.getContact(txn, c);
PublicKey handshakePublicKey = contact.getHandshakePublicKey();
if (handshakePublicKey == null) throw new DbException();
KeyPair keyPair = identityManager.getHandshakeKeys(txn);
return new Pair<>(handshakePublicKey, keyPair);
});
PublicKey theirStaticPublicKey = keys.getFirst();
KeyPair ourStaticKeyPair = keys.getSecond();
return handshake(theirStaticPublicKey, ourStaticKeyPair, in, out);
}
private HandshakeResult handshake(PublicKey theirStaticPublicKey,
KeyPair ourStaticKeyPair, InputStream in, StreamWriter out)
throws IOException {
boolean alice = transportCrypto.isAlice(theirStaticPublicKey, boolean alice = transportCrypto.isAlice(theirStaticPublicKey,
ourStaticKeyPair); ourStaticKeyPair);
RecordReader recordReader = recordReaderFactory.createRecordReader(in); RecordReader recordReader = recordReaderFactory.createRecordReader(in);

View File

@@ -101,9 +101,9 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
} }
@Override @Override
public Map<TransportId, KeySetId> addRotationKeys( public Map<TransportId, KeySetId> addRotationKeys(Transaction txn,
Transaction txn, ContactId c, SecretKey rootKey, long timestamp, ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean alice, boolean active) throws DbException { boolean active) throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>(); Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) { for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey(); TransportId t = e.getKey();
@@ -114,6 +114,14 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
return ids; return ids;
} }
@Override
public Map<TransportId, KeySetId> addRotationKeys(ContactId c,
SecretKey rootKey, long timestamp, boolean alice, boolean active)
throws DbException {
return db.transactionWithResult(false, txn ->
addRotationKeys(txn, c, rootKey, timestamp, alice, active));
}
@Override @Override
public Map<TransportId, KeySetId> addContact(Transaction txn, ContactId c, public Map<TransportId, KeySetId> addContact(Transaction txn, ContactId c,
PublicKey theirPublicKey, KeyPair ourKeyPair) PublicKey theirPublicKey, KeyPair ourKeyPair)
@@ -137,7 +145,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair) PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
throws DbException, GeneralSecurityException { throws DbException, GeneralSecurityException {
SecretKey staticMasterKey = transportCrypto SecretKey staticMasterKey = transportCrypto
.deriveStaticMasterKey(theirPublicKey, ourKeyPair); .deriveStaticMasterKey(theirPublicKey, ourKeyPair);
SecretKey rootKey = SecretKey rootKey =
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true); transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair); boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);

View File

@@ -174,6 +174,14 @@
android:value="org.briarproject.briar.android.account.NewOrRecoverActivity" /> android:value="org.briarproject.briar.android.account.NewOrRecoverActivity" />
</activity> </activity>
<activity
android:name="org.briarproject.briar.android.socialbackup.recover.RestoreAccountActivity"
android:label="@string/activity_name_restore_account"
android:parentActivityName="org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity" />
</activity>
<activity <activity
android:name="org.briarproject.briar.android.conversation.ConversationActivity" android:name="org.briarproject.briar.android.conversation.ConversationActivity"

View File

@@ -2,7 +2,7 @@ package org.briarproject.briar.android.account;
import android.content.Context; import android.content.Context;
interface DozeHelper { public interface DozeHelper {
boolean needToShowDozeFragment(Context context); boolean needToShowDozeFragment(Context context);
} }

View File

@@ -14,7 +14,7 @@ import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
class DozeView extends PowerView { public class DozeView extends PowerView {
@Nullable @Nullable
private Runnable onButtonClickListener; private Runnable onButtonClickListener;

View File

@@ -21,7 +21,7 @@ import static android.os.Build.VERSION.SDK_INT;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
class HuaweiView extends PowerView { public class HuaweiView extends PowerView {
private final static String PACKAGE_NAME = "com.huawei.systemmanager"; private final static String PACKAGE_NAME = "com.huawei.systemmanager";
private final static String CLASS_NAME = private final static String CLASS_NAME =

View File

@@ -24,7 +24,7 @@ import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
abstract class PowerView extends ConstraintLayout { public abstract class PowerView extends ConstraintLayout {
private final TextView textView; private final TextView textView;
private final ImageView checkImage; private final ImageView checkImage;
@@ -156,7 +156,7 @@ abstract class PowerView extends ConstraintLayout {
}; };
} }
interface OnCheckedChangedListener { public interface OnCheckedChangedListener {
void onCheckedChanged(); void onCheckedChanged();
} }

View File

@@ -29,6 +29,7 @@ import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PA
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public
class SetupViewModel extends AndroidViewModel { class SetupViewModel extends AndroidViewModel {
enum State {AUTHOR_NAME, SET_PASSWORD, DOZE, CREATED, FAILED} enum State {AUTHOR_NAME, SET_PASSWORD, DOZE, CREATED, FAILED}

View File

@@ -85,8 +85,10 @@ import org.briarproject.briar.android.socialbackup.CustodianSelectorFragment;
import org.briarproject.briar.android.socialbackup.DistributedBackupActivity; import org.briarproject.briar.android.socialbackup.DistributedBackupActivity;
import org.briarproject.briar.android.socialbackup.ExistingBackupFragment; import org.briarproject.briar.android.socialbackup.ExistingBackupFragment;
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity; import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity;
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardErrorFragment;
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardFragment; import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardFragment;
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardSuccessFragment; import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardSuccessFragment;
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeErrorFragment;
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeExplainerFragment; import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeExplainerFragment;
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeMainFragment; import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeMainFragment;
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity; import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity;
@@ -94,8 +96,13 @@ import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardFragm
import org.briarproject.briar.android.socialbackup.ShardsSentFragment; import org.briarproject.briar.android.socialbackup.ShardsSentFragment;
import org.briarproject.briar.android.socialbackup.ThresholdSelectorFragment; import org.briarproject.briar.android.socialbackup.ThresholdSelectorFragment;
import org.briarproject.briar.android.socialbackup.creation.CreateBackupModule; import org.briarproject.briar.android.socialbackup.creation.CreateBackupModule;
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardSuccessFragment;
import org.briarproject.briar.android.socialbackup.recover.RestoreAccountActivity;
import org.briarproject.briar.android.socialbackup.recover.RestoreAccountDozeFragment;
import org.briarproject.briar.android.socialbackup.recover.RestoreAccountSetPasswordFragment;
import org.briarproject.briar.android.splash.SplashScreenActivity; import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.briarproject.briar.android.test.TestDataActivity; import org.briarproject.briar.android.test.TestDataActivity;
import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
import dagger.Component; import dagger.Component;
@@ -206,6 +213,8 @@ public interface ActivityComponent {
void inject(OwnerRecoveryModeMainFragment ownerRecoveryModeMainFragment); void inject(OwnerRecoveryModeMainFragment ownerRecoveryModeMainFragment);
void inject(RestoreAccountActivity restoreAccountActivity);
// Fragments // Fragments
void inject(AuthorNameFragment fragment); void inject(AuthorNameFragment fragment);
@@ -287,4 +296,14 @@ public interface ActivityComponent {
void inject(OwnerReturnShardFragment ownerReturnShardFragment); void inject(OwnerReturnShardFragment ownerReturnShardFragment);
void inject(CustodianReturnShardSuccessFragment custodianReturnShardSuccessFragment); void inject(CustodianReturnShardSuccessFragment custodianReturnShardSuccessFragment);
void inject(RestoreAccountSetPasswordFragment restoreAccountSetPasswordFragment);
void inject(RestoreAccountDozeFragment restoreAccountDozeFragment);
void inject(OwnerReturnShardSuccessFragment ownerReturnShardSuccessFragment);
void inject(OwnerRecoveryModeErrorFragment ownerRecoveryModeErrorFragment);
void inject(CustodianReturnShardErrorFragment custodianReturnShardErrorFragment);
} }

View File

@@ -21,6 +21,7 @@ import javax.inject.Inject;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID; import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@@ -30,6 +31,7 @@ public class CustodianReturnShardActivity extends BriarActivity
private CustodianReturnShardViewModel viewModel; private CustodianReturnShardViewModel viewModel;
private static final Logger LOG = private static final Logger LOG =
getLogger(CustodianReturnShardActivity.class.getName()); getLogger(CustodianReturnShardActivity.class.getName());
private ContactId contactId;
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
@@ -50,33 +52,61 @@ public class CustodianReturnShardActivity extends BriarActivity
Intent intent = getIntent(); Intent intent = getIntent();
int id = intent.getIntExtra(CONTACT_ID, -1); int id = intent.getIntExtra(CONTACT_ID, -1);
if (id == -1) throw new IllegalStateException("No ContactId"); if (id == -1) throw new IllegalStateException("No ContactId");
ContactId contactId = new ContactId(id); contactId = new ContactId(id);
try { try {
viewModel.start(contactId); viewModel.start(contactId);
} catch (IOException e) { // } catch (IOException e) {
// TODO improve this // // TODO improve this
Toast.makeText(this, // Toast.makeText(this,
"It looks like you are not connected to a Wifi network", // "It looks like you are not connected to a Wifi network",
Toast.LENGTH_SHORT).show(); // Toast.LENGTH_SHORT).show();
// showInitialFragment(new CustodianReturnShardErrorFragment());
} 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",
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
finish(); finish();
} }
showInitialFragment(new CustodianRecoveryModeExplainerFragment());
} }
showInitialFragment(new CustodianRecoveryModeExplainerFragment());
viewModel.getContinueClicked().observeEvent(this, clicked -> {
if (clicked) beginTransfer();
});
viewModel.getShowCameraFragment().observeEvent(this, show -> { viewModel.getShowCameraFragment().observeEvent(this, show -> {
if (show) showCameraFragment(); if (show) showCameraFragment();
}); });
viewModel.getSuccessDismissed().observeEvent(this, dismissed -> { viewModel.getSuccessDismissed().observeEvent(this, dismissed -> {
if (dismissed) finish(); if (dismissed) finish();
}); });
viewModel.getErrorTryAgain().observeEvent(this, tryAgain -> {
if (tryAgain) beginTransfer();
});
viewModel.getState() viewModel.getState()
.observe(this, this::onReturnShardStateChanged); .observe(this, this::onReturnShardStateChanged);
} }
private void beginTransfer() {
try {
viewModel.beginTransfer();
} catch (IOException e) {
Toast.makeText(this,
"It looks like you are not connected to a Wifi network",
Toast.LENGTH_SHORT).show();
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(CustodianReturnShardFragment.TAG) == null) {
BaseFragment f = new CustodianReturnShardErrorFragment();
fm.beginTransaction()
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
.addToBackStack(f.getUniqueTag())
.commit();
}
}
}
private void onReturnShardStateChanged(CustodianTask.State state) { private void onReturnShardStateChanged(CustodianTask.State state) {
if (state instanceof CustodianTask.State.Success) { if (state instanceof CustodianTask.State.Success) {
CustodianReturnShardSuccessFragment fragment = new CustodianReturnShardSuccessFragment(); CustodianReturnShardSuccessFragment fragment = new CustodianReturnShardSuccessFragment();

View File

@@ -0,0 +1,57 @@
package org.briarproject.briar.android.socialbackup.recover;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class CustodianReturnShardErrorFragment extends BaseFragment {
public static final String TAG =
CustodianReturnShardErrorFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private CustodianReturnShardViewModel viewModel;
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(CustodianReturnShardViewModel.class);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_recovery_custodian_error_explainer,
container, false);
Button button = view.findViewById(R.id.button);
button.setOnClickListener(e -> viewModel.onErrorTryAgain());
// TODO cancel button
// button.setOnClickListener(e -> viewModel.onErrorCancelled());
return view;
}
@Override
public String getUniqueTag() {
return TAG;
}
}

View File

@@ -51,13 +51,14 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
private final SocialBackupManager socialBackupManager; private final SocialBackupManager socialBackupManager;
private final DatabaseComponent db; private final DatabaseComponent db;
final QrCodeDecoder qrCodeDecoder; final QrCodeDecoder qrCodeDecoder;
private boolean wasContinueClicked = false;
private boolean qrCodeRead = false; private boolean qrCodeRead = false;
private WifiManager wifiManager; private WifiManager wifiManager;
private final MutableLiveEvent<Boolean > continueClicked = new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> showCameraFragment = private final MutableLiveEvent<Boolean> showCameraFragment =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> successDismissed = private final MutableLiveEvent<Boolean> successDismissed =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> errorTryAgain = new MutableLiveEvent<>();
private final MutableLiveData<CustodianTask.State> state = private final MutableLiveData<CustodianTask.State> state =
new MutableLiveData<>(); new MutableLiveData<>();
private final CustodianTask task; private final CustodianTask task;
@@ -110,12 +111,8 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
} }
} }
public void start(ContactId contactId) throws DbException, IOException { public void start(ContactId contactId) throws DbException {
InetAddress inetAddress = getWifiIpv4Address(); // TODO this should be transactionWithResult
LOG.info("Client InetAddress: " + inetAddress);
if (inetAddress == null)
throw new IOException("Cannot get IP on local wifi");
db.transaction(false, txn -> { db.transaction(false, txn -> {
if (!socialBackupManager.amCustodian(txn, contactId)) { if (!socialBackupManager.amCustodian(txn, contactId)) {
throw new DbException(); throw new DbException();
@@ -123,8 +120,6 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
returnShardPayloadBytes = socialBackupManager returnShardPayloadBytes = socialBackupManager
.getReturnShardPayloadBytes(txn, contactId); .getReturnShardPayloadBytes(txn, contactId);
}); });
task.cancel();
task.start(this, returnShardPayloadBytes);
} }
@IoExecutor @IoExecutor
@@ -149,11 +144,31 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
} }
} }
public void beginTransfer() throws IOException {
InetAddress inetAddress = getWifiIpv4Address();
LOG.info("Client InetAddress: " + inetAddress);
if (inetAddress == null)
throw new IOException("Cannot get IP on local wifi");
task.cancel();
task.start(this, returnShardPayloadBytes);
//TODO camera permissions
showCameraFragment.setEvent(true);
}
@UiThread @UiThread
public void onContinueClicked() { public void onContinueClicked() {
wasContinueClicked = true; continueClicked.setEvent(true);
// checkPermissions.setEvent(true); // checkPermissions.setEvent(true);
showCameraFragment.setEvent(true); }
@UiThread
public void onErrorCancelled() {
errorTryAgain.postEvent(false);
}
@UiThread
public void onErrorTryAgain() {
errorTryAgain.postEvent(true);
} }
@UiThread @UiThread
@@ -186,4 +201,10 @@ public class CustodianReturnShardViewModel extends AndroidViewModel
qrCodeRead = true; qrCodeRead = true;
} }
} }
public MutableLiveEvent<Boolean> getErrorTryAgain() {
return errorTryAgain;
}
public MutableLiveEvent<Boolean> getContinueClicked() { return continueClicked; }
} }

View File

@@ -0,0 +1,57 @@
package org.briarproject.briar.android.socialbackup.recover;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class OwnerRecoveryModeErrorFragment extends BaseFragment {
public static final String TAG =
OwnerRecoveryModeErrorFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private OwnerReturnShardViewModel viewModel;
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(OwnerReturnShardViewModel.class);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_recovery_custodian_error_explainer,
container, false);
Button button = view.findViewById(R.id.button);
button.setOnClickListener(e -> viewModel.onErrorTryAgain());
// TODO cancel button
// button.setOnClickListener(e -> viewModel.onErrorCancelled());
return view;
}
@Override
public String getUniqueTag() {
return TAG;
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.socialbackup.recover; package org.briarproject.briar.android.socialbackup.recover;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
@@ -23,6 +24,10 @@ import javax.inject.Inject;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -72,6 +77,12 @@ public class OwnerReturnShardActivity extends BaseActivity
showNextFragment(new OwnerRecoveryModeMainFragment()); showNextFragment(new OwnerRecoveryModeMainFragment());
} }
}); });
viewModel.getSuccessDismissed().observeEvent(this, success -> {
if (success) onSuccessDismissed();
});
viewModel.getErrorTryAgain().observeEvent(this, tryAgain -> {
if (tryAgain) onBackPressed();
});
viewModel.getState() viewModel.getState()
.observe(this, this::onReturnShardStateChanged); .observe(this, this::onReturnShardStateChanged);
} }
@@ -120,6 +131,13 @@ public class OwnerReturnShardActivity extends BaseActivity
} }
} }
private void onSuccessDismissed() {
finish();
Intent i = new Intent(this, RestoreAccountActivity.class);
i.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
startActivity(i);
}
private void onReturnShardStateChanged(SecretOwnerTask.State state) { private void onReturnShardStateChanged(SecretOwnerTask.State state) {
if (state instanceof SecretOwnerTask.State.Success) { if (state instanceof SecretOwnerTask.State.Success) {
@@ -131,9 +149,8 @@ public class OwnerReturnShardActivity extends BaseActivity
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
if (added && viewModel.canRecover()) { if (added && viewModel.canRecover()) {
LOG.info("Secret key recovered"); LOG.info("Secret key recovered");
int version = 0;
try { try {
version = viewModel.recover(); viewModel.recover();
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
LOG.warning("Unable to decrypt backup" + e.toString()); LOG.warning("Unable to decrypt backup" + e.toString());
Toast.makeText(this, Toast.makeText(this,
@@ -148,20 +165,16 @@ public class OwnerReturnShardActivity extends BaseActivity
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
return; return;
} }
Toast.makeText(this, showNextFragment(new OwnerReturnShardSuccessFragment());
"Account recovered! " + version,
Toast.LENGTH_LONG).show();
finish();
return; return;
} }
onBackPressed(); onBackPressed();
} else if (state instanceof SecretOwnerTask.State.Failure) { } else if (state instanceof SecretOwnerTask.State.Failure) {
// TODO error screen, handle reason // Toast.makeText(this,
Toast.makeText(this, // "Shard return failed!",
"Shard return failed!", // Toast.LENGTH_SHORT).show();
Toast.LENGTH_SHORT).show(); // onBackPressed();
onBackPressed(); showNextFragment(new OwnerRecoveryModeErrorFragment());
// showNextFragment(new OwnerRecoveryModeExplainerFragment());
} }
} }

View File

@@ -68,7 +68,7 @@ public class OwnerReturnShardFragment extends BaseFragment
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_keyagreement_qr, container, return inflater.inflate(R.layout.fragment_recovery_owner_qr, container,
false); false);
} }
@@ -134,13 +134,13 @@ public class OwnerReturnShardFragment extends BaseFragment
private void onReturnShardStateChanged( private void onReturnShardStateChanged(
@Nullable SecretOwnerTask.State state) { @Nullable SecretOwnerTask.State state) {
if (state instanceof SecretOwnerTask.State.Listening) { if (state instanceof SecretOwnerTask.State.Listening) {
status.setText(R.string.waiting_for_contact_to_scan);
Bitmap qrCode = viewModel.getQrCodeBitmap(); Bitmap qrCode = viewModel.getQrCodeBitmap();
qrCodeView.setQrCode(qrCode); qrCodeView.setQrCode(qrCode);
} else if (state instanceof SecretOwnerTask.State.ReceivingShard) { } else if (state instanceof SecretOwnerTask.State.ReceivingShard) {
statusView.setVisibility(VISIBLE);
status.setText(R.string.connecting_to_device); status.setText(R.string.connecting_to_device);
} else if (state instanceof SecretOwnerTask.State.SendingAck) { } else if (state instanceof SecretOwnerTask.State.SendingAck) {
status.setText(R.string.waiting_for_contact_to_scan); status.setText(R.string.recovery_sending_ack);
} else if (state instanceof SecretOwnerTask.State.Success) { } else if (state instanceof SecretOwnerTask.State.Success) {
status.setText("Success"); status.setText("Success");
} else if (state instanceof SecretOwnerTask.State.Failure) { } else if (state instanceof SecretOwnerTask.State.Failure) {

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.socialbackup.recover; package org.briarproject.briar.android.socialbackup.recover;
import org.briarproject.briar.android.viewmodel.ViewModelKey; import org.briarproject.briar.android.viewmodel.ViewModelKey;
import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import dagger.Binds; import dagger.Binds;
@@ -17,4 +18,9 @@ public abstract class OwnerReturnShardModule {
abstract ViewModel bindOwnerReturnShardViewModel( abstract ViewModel bindOwnerReturnShardViewModel(
OwnerReturnShardViewModel ownerReturnShardViewModel); OwnerReturnShardViewModel ownerReturnShardViewModel);
@Binds
@IntoMap
@ViewModelKey(RestoreAccountViewModel.class)
abstract ViewModel bindRestoreAccountViewModel(
RestoreAccountViewModel restoreAccountViewModel);
} }

View File

@@ -0,0 +1,54 @@
package org.briarproject.briar.android.socialbackup.recover;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
public class OwnerReturnShardSuccessFragment extends BaseFragment {
public static final String TAG =
OwnerReturnShardSuccessFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private OwnerReturnShardViewModel viewModel;
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(OwnerReturnShardViewModel.class);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_recovery_owner_success,
container, false);
Button button = view.findViewById(R.id.button);
button.setOnClickListener(e -> viewModel.onSuccessDismissed());
return view;
}
@Override
public String getUniqueTag() {
return TAG;
}
}

View File

@@ -14,19 +14,14 @@ 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.BackupPayload;
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.RestoreAccount;
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask; import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
import org.briarproject.briar.socialbackup.BackupPayloadDecoder;
import org.briarproject.briar.socialbackup.SocialBackup;
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.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -51,23 +46,22 @@ class OwnerReturnShardViewModel extends AndroidViewModel
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19 @SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
// private ReturnShardPayload returnShardPayload;
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 RestoreAccount restoreAccount;
private final BackupPayloadDecoder backupPayloadDecoder;
private final MutableLiveEvent<Boolean> errorTryAgain =
new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> showQrCodeFragment = private final MutableLiveEvent<Boolean> showQrCodeFragment =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> successDismissed = new MutableLiveEvent<>();
private final MutableLiveData<SecretOwnerTask.State> state = private final MutableLiveData<SecretOwnerTask.State> state =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveEvent<Boolean> startClicked = private final MutableLiveEvent<Boolean> startClicked =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private boolean wasContinueClicked = false; private boolean wasContinueClicked = false;
private boolean isActivityResumed = false; private boolean isActivityResumed = false;
private ArrayList<ReturnShardPayload> recoveredShards = new ArrayList<>();
private Bitmap qrCodeBitmap; private Bitmap qrCodeBitmap;
private WifiManager wifiManager; private WifiManager wifiManager;
private SecretKey secretKey; private SecretKey secretKey;
@@ -76,14 +70,12 @@ class OwnerReturnShardViewModel extends AndroidViewModel
OwnerReturnShardViewModel(Application app, OwnerReturnShardViewModel(Application app,
AndroidExecutor androidExecutor, AndroidExecutor androidExecutor,
SecretOwnerTask task, SecretOwnerTask task,
DarkCrystal darkCrystal, RestoreAccount restoreAccount,
BackupPayloadDecoder backupPayloadDecoder,
@IoExecutor Executor ioExecutor) { @IoExecutor Executor ioExecutor) {
super(app); super(app);
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.backupPayloadDecoder = backupPayloadDecoder; this.restoreAccount = restoreAccount;
this.darkCrystal = darkCrystal;
this.task = task; this.task = task;
wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE); wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE);
@@ -132,6 +124,11 @@ class OwnerReturnShardViewModel extends AndroidViewModel
startShardReturn(); startShardReturn();
} }
@UiThread
void onSuccessDismissed() {
successDismissed.setEvent(true);
}
@UiThread @UiThread
void startShardReturn() { void startShardReturn() {
// If we return to the intro fragment, the continue button needs to be // If we return to the intro fragment, the continue button needs to be
@@ -163,6 +160,10 @@ class OwnerReturnShardViewModel extends AndroidViewModel
}); });
} }
@UiThread
public void onErrorTryAgain() {
errorTryAgain.setEvent(true);
}
/** /**
* Set to true in onPostResume() and false in onPause(). This prevents the * Set to true in onPostResume() and false in onPause(). This prevents the
@@ -196,7 +197,7 @@ class OwnerReturnShardViewModel extends AndroidViewModel
} }
public int getNumberOfShards() { public int getNumberOfShards() {
return recoveredShards.size(); return restoreAccount.getNumberOfShards();
} }
@Override @Override
@@ -227,38 +228,22 @@ class OwnerReturnShardViewModel extends AndroidViewModel
// TODO figure out how to actually use a hash set for these objects // TODO figure out how to actually use a hash set for these objects
public boolean addToShardSet(ReturnShardPayload toAdd) { public boolean addToShardSet(ReturnShardPayload toAdd) {
boolean found = false; return restoreAccount.addReturnShardPayload(toAdd);
for (ReturnShardPayload returnShardPayload : recoveredShards) {
if (toAdd.equals(returnShardPayload)) {
found = true;
break;
}
}
if (!found) recoveredShards.add(toAdd);
return !found;
} }
public boolean canRecover() { public boolean canRecover() {
ArrayList<Shard> shards = new ArrayList(); return restoreAccount.canRecover();
for (ReturnShardPayload returnShardPayload : recoveredShards) {
// TODO check shards all have same secret id
shards.add(returnShardPayload.getShard());
}
try {
secretKey = darkCrystal.combineShards(shards);
} catch (GeneralSecurityException e) {
// TODO handle error message
return false;
}
return true;
} }
public int recover() throws FormatException, GeneralSecurityException { public int recover() throws FormatException, GeneralSecurityException {
if (secretKey == null) throw new GeneralSecurityException(); return restoreAccount.recover();
// TODO find backup with highest version number }
BackupPayload backupPayload = recoveredShards.get(0).getBackupPayload();
SocialBackup decodedBackup = backupPayloadDecoder.decodeBackupPayload(secretKey, backupPayload); public MutableLiveEvent<Boolean> getSuccessDismissed() {
int version = decodedBackup.getVersion(); return successDismissed;
return version; }
public MutableLiveEvent<Boolean> getErrorTryAgain() {
return errorTryAgain;
} }
} }

View File

@@ -0,0 +1,81 @@
package org.briarproject.briar.android.socialbackup.recover;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Bundle;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.android.socialbackup.recover.RestoreAccountViewModel.State;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class RestoreAccountActivity extends BaseActivity
implements BaseFragment.BaseFragmentListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
RestoreAccountViewModel viewModel;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(RestoreAccountViewModel.class);
viewModel.getState().observeEvent(this, this::onStateChanged);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_fragment_container);
}
private void onStateChanged(RestoreAccountViewModel.State state) {
if (state == State.SET_PASSWORD) {
showInitialFragment(RestoreAccountSetPasswordFragment.newInstance());
} else if (state == State.DOZE) {
showDozeFragment();
} else if (state == State.CREATED || state == State.FAILED) {
// TODO: Show an error if failed
showApp();
}
}
@TargetApi(23)
void showDozeFragment() {
showNextFragment(RestoreAccountDozeFragment.newInstance());
}
void showApp() {
Intent i = new Intent(this, ENTRY_ACTIVITY);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME |
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
supportFinishAfterTransition();
overridePendingTransition(R.anim.screen_new_in, R.anim.screen_old_out);
}
@Override
@Deprecated
public void runOnDbThread(Runnable runnable) {
throw new RuntimeException("Don't use this deprecated method here.");
}
}

View File

@@ -0,0 +1,118 @@
package org.briarproject.briar.android.socialbackup.recover;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.account.DozeView;
import org.briarproject.briar.android.account.HuaweiView;
import org.briarproject.briar.android.account.PowerView;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.util.UiUtils;
import androidx.annotation.Nullable;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class RestoreAccountDozeFragment extends RestoreAccountFragment
implements PowerView.OnCheckedChangedListener {
private final static String TAG = RestoreAccountDozeFragment.class.getName();
private DozeView dozeView;
private HuaweiView huaweiView;
private Button next;
private boolean secondAttempt = false;
public static RestoreAccountDozeFragment newInstance() {
return new RestoreAccountDozeFragment();
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_doze_title));
setHasOptionsMenu(false);
View v = inflater.inflate(R.layout.fragment_setup_doze, container,
false);
dozeView = v.findViewById(R.id.dozeView);
dozeView.setOnCheckedChangedListener(this);
huaweiView = v.findViewById(R.id.huaweiView);
huaweiView.setOnCheckedChangedListener(this);
next = v.findViewById(R.id.next);
ProgressBar progressBar = v.findViewById(R.id.progress);
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
next.setOnClickListener(this);
viewModel.getIsCreatingAccount()
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
if (isCreatingAccount) {
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
});
return v;
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
protected String getHelpText() {
return getString(R.string.setup_doze_explanation);
}
@Override
public void onActivityResult(int request, int result,
@Nullable Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_DOZE_WHITELISTING) {
if (!dozeView.needsToBeShown() || secondAttempt) {
dozeView.setChecked(true);
} else if (getContext() != null) {
secondAttempt = true;
showOnboardingDialog(getContext(), getHelpText());
}
}
}
@Override
public void onCheckedChanged() {
next.setEnabled(dozeView.isChecked() && huaweiView.isChecked());
}
@SuppressLint("BatteryLife")
private void askForDozeWhitelisting() {
if (getContext() == null) return;
Intent i = UiUtils.getDozeWhitelistingIntent(getContext());
startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
}
@Override
public void onClick(View view) {
viewModel.dozeExceptionConfirmed();
}
}

View File

@@ -0,0 +1,91 @@
package org.briarproject.briar.android.socialbackup.recover;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
import static org.briarproject.briar.android.util.UiUtils.enterPressed;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
abstract class RestoreAccountFragment extends BaseFragment implements TextWatcher,
TextView.OnEditorActionListener, View.OnClickListener {
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
@Inject
ViewModelProvider.Factory viewModelFactory;
RestoreAccountViewModel viewModel;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(requireActivity())
.get(RestoreAccountViewModel.class);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.help_action, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_help) {
showOnboardingDialog(getContext(), getHelpText());
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
protected abstract String getHelpText();
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// noop
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// noop
}
@Override
public boolean onEditorAction(TextView textView, int actionId,
@Nullable KeyEvent keyEvent) {
if (actionId == IME_ACTION_NEXT || actionId == IME_ACTION_DONE ||
enterPressed(actionId, keyEvent)) {
onClick(textView);
return true;
}
return false;
}
@Override
public void afterTextChanged(Editable editable) {
// noop
}
}

View File

@@ -0,0 +1,131 @@
package org.briarproject.briar.android.socialbackup.recover;
import android.os.Bundle;
import android.os.IBinder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.ProgressBar;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.login.StrengthMeter;
import javax.annotation.Nullable;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.briar.android.util.UiUtils.setError;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class RestoreAccountSetPasswordFragment extends RestoreAccountFragment {
private final static String TAG = RestoreAccountSetPasswordFragment.class.getName();
private TextInputLayout passwordEntryWrapper;
private TextInputLayout passwordConfirmationWrapper;
private TextInputEditText passwordEntry;
private TextInputEditText passwordConfirmation;
private StrengthMeter strengthMeter;
private Button nextButton;
public static RestoreAccountSetPasswordFragment newInstance() {
return new RestoreAccountSetPasswordFragment();
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container,
false);
strengthMeter = v.findViewById(R.id.strength_meter);
passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper);
passwordEntry = v.findViewById(R.id.password_entry);
passwordConfirmationWrapper =
v.findViewById(R.id.password_confirm_wrapper);
passwordConfirmation = v.findViewById(R.id.password_confirm);
nextButton = v.findViewById(R.id.next);
ProgressBar progressBar = v.findViewById(R.id.progress);
passwordEntry.addTextChangedListener(this);
passwordConfirmation.addTextChangedListener(this);
nextButton.setOnClickListener(this);
if (!viewModel.needToShowDozeFragment()) {
nextButton.setText(R.string.create_account_button);
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
}
viewModel.getIsCreatingAccount()
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
if (isCreatingAccount) {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
// this also avoids the keyboard popping up
passwordEntry.setFocusable(false);
passwordConfirmation.setFocusable(false);
}
});
return v;
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
protected String getHelpText() {
return getString(R.string.setup_password_explanation);
}
@Override
public void onTextChanged(CharSequence authorName, int i, int i1, int i2) {
String password1 = passwordEntry.getText().toString();
String password2 = passwordConfirmation.getText().toString();
boolean passwordsMatch = password1.equals(password2);
strengthMeter
.setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE);
float strength = viewModel.estimatePasswordStrength(password1);
strengthMeter.setStrength(strength);
boolean strongEnough = strength >= QUITE_WEAK;
setError(passwordEntryWrapper, getString(R.string.password_too_weak),
password1.length() > 0 && !strongEnough);
setError(passwordConfirmationWrapper,
getString(R.string.passwords_do_not_match),
password2.length() > 0 && !passwordsMatch);
boolean enabled = passwordsMatch && strongEnough;
nextButton.setEnabled(enabled);
passwordConfirmation.setOnEditorActionListener(enabled ? this : null);
}
@Override
public void onClick(View view) {
IBinder token = passwordEntry.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
viewModel.setPassword(passwordEntry.getText().toString());
}
}

View File

@@ -0,0 +1,133 @@
package org.briarproject.briar.android.socialbackup.recover;
import android.app.Application;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.account.DozeHelper;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.socialbackup.SocialBackup;
import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Logger.getLogger;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class RestoreAccountViewModel extends AndroidViewModel {
enum State {SET_PASSWORD, DOZE, CREATED, FAILED}
private static final Logger LOG =
getLogger(RestoreAccountViewModel.class.getName());
@Nullable
private String password;
private final MutableLiveEvent<State>
state = new MutableLiveEvent<>();
private final MutableLiveData<Boolean> isCreatingAccount =
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;
@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;
ioExecutor.execute(() -> {
if (accountManager.accountExists()) {
throw new AssertionError();
} else {
state.postEvent(State.SET_PASSWORD);
}
});
}
LiveEvent<State> getState() {
return state;
}
LiveData<Boolean> getIsCreatingAccount() {
return isCreatingAccount;
}
void setPassword(String password) {
this.password = password;
if (needToShowDozeFragment()) {
state.setEvent(State.DOZE);
} else {
createAccount();
}
}
float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
boolean needToShowDozeFragment() {
return dozeHelper.needToShowDozeFragment(getApplication());
}
void dozeExceptionConfirmed() {
createAccount();
}
private void createAccount() {
if (password == null) throw new IllegalStateException();
isCreatingAccount.setValue(true);
SocialBackup socialBackup = restoreAccount.getSocialBackup();
if (socialBackup == null) {
LOG.warning("Cannot retrieve social backup");
state.postEvent(State.FAILED);
}
Identity identity = socialBackup.getIdentity();
ioExecutor.execute(() -> {
if (accountManager.restoreAccount(identity, password)) {
LOG.info("Restored account");
try {
restoreAccount.addContactsToDb();
} catch (DbException e) {
LOG.warning("Cannot retrieve social backup");
state.postEvent(State.FAILED);
}
state.postEvent(State.CREATED);
} else {
LOG.warning("Failed to create account");
state.postEvent(State.FAILED);
}
});
}
}

View File

@@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="409dp"
android:height="162dp"
android:viewportHeight="161.7"
android:viewportWidth="409.2">
<path
android:fillColor="#FF000000"
android:pathData="M369.8,157.4l-4.3,-4.3l-7.1,-2.4c-3.9,-1.3 -8.7,-3 -10.7,-3.7l-3.7,-1.3l3.5,-0.2c8.2,-0.4 13,-4 14.3,-10.9c0.8,-4.1 1.1,-17.3 0.8,-33c-0.2,-8.1 -0.2,-15.4 0,-16.3c0.1,-0.9 0.5,-2.4 0.9,-3.4c1.2,-3.5 0.3,-11.9 -1.9,-17.6c-0.3,-0.9 -1.9,-4.2 -3.5,-7.4c-4.2,-8.2 -4.5,-8.9 -4.9,-10.5c-0.5,-1.8 -0.2,-5.4 0.5,-6.8c0.7,-1.3 2.2,-2.9 3.2,-3.5c1.3,-0.7 2.6,0.1 4.7,2.9c3.4,4.5 14,19.4 15.7,22.2c3.7,6 6,11.2 8,18.8c0.7,2.5 1.9,7 2.7,10.1c0.8,3.1 2.7,10.2 4.1,15.8l2.6,10.2l4.6,5.2c2.6,2.9 5.8,6.5 7.2,8c1.4,1.6 2.5,3 2.5,3.2c0,0.3 -34.5,29.3 -34.9,29.3C374.2,161.7 372.2,159.7 369.8,157.4zM275.9,141c-1.3,-0.6 -2.2,-1.4 -2.9,-2.3c-2.1,-2.7 -2,2.4 -1.9,-68.5l0.1,-64l0.7,-1.2c1,-1.9 2,-2.9 3.7,-3.9l1.6,-0.9l37.8,-0.1c42.5,-0.1 39.4,-0.2 42.1,2.2c0.9,0.8 1.8,2 2.2,2.9c0.7,1.6 0.7,1.6 0.8,14.2l0.1,12.6l-1.8,-0.1c-1.4,-0.1 -2.1,0 -3.2,0.5c-2,1 -3.9,2.9 -5.1,5.1l-1,2l0,-12.8l0,-12.8h-33.6h-33.6v51.3v51.3h33.6h33.6l0.1,-34.4c0.1,-33 0.1,-34.4 0.6,-32.9c0.3,0.8 1.8,4 3.4,7c5.5,10.6 5.4,9.9 5.4,47.2c0,27.6 -0.1,30 -1.7,33.1c-1.1,2.2 -2.7,3.7 -5.1,4.7l-1.7,0.7L314,141.8l-36.2,0.1L275.9,141L275.9,141zM318.3,135.9c2.9,-1.3 4.5,-3.7 4.4,-6.6c0,-4.1 -3.1,-7.2 -7.1,-7.2c-2.1,0 -3.6,0.6 -5.2,2.2c-2.2,2.2 -2.8,5.4 -1.3,8.3c0.7,1.4 2.5,3 4,3.5C314.6,136.6 317,136.6 318.3,135.9z"/>
<path
android:fillColor="#FF000000"
android:pathData="M39.4,157.4l4.3,-4.3l7.1,-2.4c3.9,-1.3 8.7,-3 10.7,-3.7l3.7,-1.3l-3.5,-0.2c-8.2,-0.4 -13,-4 -14.3,-10.9c-0.8,-4.1 -1.1,-17.3 -0.8,-33c0.2,-8.1 0.2,-15.4 0,-16.3c-0.1,-0.9 -0.5,-2.4 -0.9,-3.4c-1.2,-3.5 -0.3,-11.9 1.9,-17.6c0.3,-0.9 1.9,-4.2 3.5,-7.4c4.2,-8.2 4.5,-8.9 4.9,-10.5c0.5,-1.8 0.2,-5.4 -0.5,-6.8c-0.7,-1.3 -2.2,-2.9 -3.2,-3.5c-1.3,-0.7 -2.6,0.1 -4.7,2.9c-3.4,4.5 -14,19.4 -15.7,22.2c-3.7,6 -6,11.2 -8,18.8c-0.7,2.5 -1.9,7 -2.7,10.1c-0.8,3.1 -2.7,10.2 -4.1,15.8l-2.6,10.2l-4.6,5.2c-2.6,2.9 -5.8,6.5 -7.2,8s-2.5,3 -2.5,3.2c0,0.3 34.5,29.3 34.9,29.3C35,161.7 37.1,159.7 39.4,157.4zM133.3,141c1.3,-0.6 2.2,-1.4 2.9,-2.3c2.1,-2.7 2,2.4 1.9,-68.5l-0.1,-64l-0.7,-1.2c-1,-1.9 -2,-2.9 -3.7,-3.9l-1.6,-0.9l-37.8,-0.1c-42.5,-0.1 -39.4,-0.2 -42.1,2.2c-0.9,0.8 -1.8,2 -2.2,2.9c-0.7,1.6 -0.7,1.6 -0.8,14.2L49,32l1.8,-0.1c1.4,-0.1 2.1,0 3.2,0.5c2,1 3.9,2.9 5.1,5.1l1,2l0,-12.8l0,-12.8h33.6h33.6v51.3v51.3L93.8,116.5L60.2,116.5l-0.1,-34.4c-0.1,-33 -0.1,-34.4 -0.6,-32.9c-0.3,0.8 -1.8,4 -3.4,7c-5.5,10.6 -5.4,9.9 -5.4,47.2c0,27.6 0.1,30 1.7,33.1c1.1,2.2 2.7,3.7 5.1,4.7l1.7,0.7l36.2,0.1l36.2,0.1L133.3,141L133.3,141zM90.9,135.9c-2.9,-1.3 -4.5,-3.7 -4.4,-6.6c0,-4.1 3.1,-7.2 7.1,-7.2c2.1,0 3.6,0.6 5.2,2.2c2.2,2.2 2.8,5.4 1.3,8.3c-0.7,1.4 -2.5,3 -4,3.5C94.6,136.6 92.3,136.6 90.9,135.9z"/>
<path
android:fillColor="#FF000000"
android:pathData="M80.5,63h2.3v2.3h2.3v2.3L73.6,67.6v-2.3h2.3v-4.6h4.6L80.5,63L80.5,63zM110.5,83.8h2.3v-2.3h-2.3L110.5,83.8zM82.8,63h2.3v-2.3h-2.3L82.8,63zM115.1,83.8h2.3v-2.3h-2.3L115.1,83.8zM87.4,86.1L92,86.1L92,83.8h-4.6L87.4,86.1zM108.2,86.1L108.2,83.8h-2.3v2.3L108.2,86.1zM99,86.1h2.3v-4.6L99,81.5L99,86.1zM80.5,56.1v2.3h6.9v-2.3L80.5,56.1zM78.2,58.4v-2.3h-4.6v4.6h2.3v-2.3L78.2,58.4zM85.1,53.8L69,53.8v-16.1h16.1L85.1,53.8zM82.8,40L71.3,40v11.5h11.5L82.8,40zM73.6,81.5h6.9v-6.9h-6.9L73.6,81.5zM96.6,79.1v2.3L99,81.4v-2.3L96.6,79.1zM80.5,42.3h-6.9v6.9h6.9L80.5,42.3zM117.4,37.7L117.4,53.8L101.3,53.8v-16.1L117.4,37.7zM115.1,40L103.6,40v11.5h11.5L115.1,40zM69,69.9h16.1v16.1L69,86L69,69.9zM71.3,83.8h11.5v-11.5L71.3,72.3L71.3,83.8zM71.3,56.1L69,56.1v11.5h2.3L71.3,56.1zM101.3,67.6v2.3h2.3v-2.3L101.3,67.6zM94.3,76.9v-2.3L92,74.6v2.3h-4.6v4.6L92,81.5v2.3h2.3v-4.6h2.3v-2.3L94.3,76.9zM87.4,46.9L92,46.9v-2.3h-4.6L87.4,46.9zM105.9,65.3h4.6v2.3h2.3v-6.9h-2.3v-4.6h-2.3v6.9h-6.9v2.3h2.3v2.3h2.3L105.9,65.3zM108.2,72.2h-2.3v-2.3h-2.3v4.6h-6.9v2.3h4.6v4.6h2.3v2.3h2.3v-4.6h9.2v-2.3h-6.9L108.2,72.2zM108.2,72.2h2.3v-4.6h-2.3L108.2,72.2zM89.7,72.2v-2.3L92,69.9v-2.3h2.3v-2.3h2.3v-4.6h6.9v-4.6h-2.3v2.3L99,58.4v-9.2h-2.3v-4.6L99,44.6v-6.9h-2.3v4.6h-2.3v-4.6h-6.9v4.6h2.3v-2.3L92,40v4.6h2.3v6.9h2.3v2.3h-2.3v4.6L92,58.4L92,53.8h-2.3v-2.3h-2.3v4.6h2.3v2.3h-2.3v6.9h2.3v-4.6L92,60.7v4.6h-2.3v2.3h-2.3v6.9L92,74.5v-2.3L89.7,72.2zM115.1,74.5v-2.3h-4.6v2.3L115.1,74.5zM112.8,42.3h-6.9v6.9h6.9L112.8,42.3zM94.3,72.2L99,72.2v-2.3h-2.3v-2.3h-2.3L94.4,72.2zM99,67.6v-2.3h-2.3v2.3L99,67.6zM112.8,58.4h4.6v-2.3h-4.6L112.8,58.4zM115.1,76.9h2.3v-2.3h-2.3L115.1,76.9zM115.1,63h2.3v-2.3h-2.3L115.1,63zM94.3,51.5L92,51.5v2.3h2.3L94.3,51.5zM94.3,51.5"/>
<!-- <path-->
<!-- android:fillColor="#FF000000"-->
<!-- android:pathData="M303.5,63h2.3v2.3h2.3v2.3h-11.5v-2.3h2.3v-4.6h4.6L303.5,63L303.5,63zM333.5,83.8h2.3v-2.3h-2.3L333.5,83.8zM305.8,63h2.3v-2.3h-2.3L305.8,63zM338.1,83.8h2.3v-2.3h-2.3L338.1,83.8zM310.4,86.1h4.6L315,83.8h-4.6L310.4,86.1zM331.2,86.1L331.2,83.8h-2.3v2.3L331.2,86.1zM322,86.1h2.3v-4.6L322,81.5L322,86.1zM303.5,56.1v2.3h6.9v-2.3L303.5,56.1zM301.2,58.4v-2.3h-4.6v4.6h2.3v-2.3L301.2,58.4zM308.1,53.8L292,53.8v-16.1h16.1L308.1,53.8zM305.8,40h-11.5v11.5h11.5L305.8,40zM296.6,81.5h6.9v-6.9h-6.9L296.6,81.5zM319.6,79.1v2.3h2.3v-2.3L319.6,79.1zM303.5,42.3h-6.9v6.9h6.9L303.5,42.3zM340.4,37.7L340.4,53.8h-16.1v-16.1L340.4,37.7zM338.1,40h-11.5v11.5h11.5L338.1,40zM292,69.9h16.1v16.1L292,86L292,69.9zM294.3,83.8h11.5v-11.5h-11.5L294.3,83.8zM294.3,56.1L292,56.1v11.5h2.3L294.3,56.1zM324.3,67.6v2.3h2.3v-2.3L324.3,67.6zM317.3,76.9v-2.3L315,74.6v2.3h-4.6v4.6h4.6v2.3h2.3v-4.6h2.3v-2.3L317.3,76.9zM310.4,46.9h4.6v-2.3h-4.6L310.4,46.9zM328.9,65.3h4.6v2.3h2.3v-6.9h-2.3v-4.6h-2.3v6.9h-6.9v2.3h2.3v2.3h2.3L328.9,65.3zM331.2,72.2h-2.3v-2.3h-2.3v4.6h-6.9v2.3h4.6v4.6h2.3v2.3h2.3v-4.6h9.2v-2.3h-6.9L331.2,72.2zM331.2,72.2h2.3v-4.6h-2.3L331.2,72.2zM312.7,72.2v-2.3h2.3v-2.3h2.3v-2.3h2.3v-4.6h6.9v-4.6h-2.3v2.3L322,58.4v-9.2h-2.3v-4.6h2.3v-6.9h-2.3v4.6h-2.3v-4.6h-6.9v4.6h2.3v-2.3h2.3v4.6h2.3v6.9h2.3v2.3h-2.3v4.6L315,58.4L315,53.8h-2.3v-2.3h-2.3v4.6h2.3v2.3h-2.3v6.9h2.3v-4.6h2.3v4.6h-2.3v2.3h-2.3v6.9h4.6v-2.3L312.7,72.2zM338.1,74.5v-2.3h-4.6v2.3L338.1,74.5zM335.8,42.3h-6.9v6.9h6.9L335.8,42.3zM317.3,72.2h4.6v-2.3h-2.3v-2.3h-2.3L317.3,72.2zM322,67.6v-2.3h-2.3v2.3L322,67.6zM335.8,58.4h4.6v-2.3h-4.6L335.8,58.4zM338.1,76.9h2.3v-2.3h-2.3L338.1,76.9zM338.1,63h2.3v-2.3h-2.3L338.1,63zM317.3,51.5L315,51.5v2.3h2.3L317.3,51.5zM317.3,51.5"/>-->
<path
android:fillColor="#FF000000"
android:pathData="M179.6,48.9l-20.6,18l20.6,16.7v-5.2L199,78.4v-24.3h-19.3L179.7,48.9z"/>
<path
android:fillColor="#FF000000"
android:pathData="M229.4,83.7l20.6,-18l-20.6,-16.7v5.2L210,54.2v24.3h19.3L229.3,83.7z"/>
</vector>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="409.2dp"
android:height="161.7dp"
android:viewportHeight="161.7"
android:viewportWidth="409.2">
<path
android:fillColor="#FF000000"
android:pathData="M369.8,157.4l-4.3,-4.3l-7.1,-2.4c-3.9,-1.3 -8.7,-3 -10.7,-3.7l-3.7,-1.3l3.5,-0.2c8.2,-0.4 13,-4 14.3,-10.9c0.8,-4.1 1.1,-17.3 0.8,-33c-0.2,-8.1 -0.2,-15.4 0,-16.3c0.1,-0.9 0.5,-2.4 0.9,-3.4c1.2,-3.5 0.3,-11.9 -1.9,-17.6c-0.3,-0.9 -1.9,-4.2 -3.5,-7.4c-4.2,-8.2 -4.5,-8.9 -4.9,-10.5c-0.5,-1.8 -0.2,-5.4 0.5,-6.8c0.7,-1.3 2.2,-2.9 3.2,-3.5c1.3,-0.7 2.6,0.1 4.7,2.9c3.4,4.5 14,19.4 15.7,22.2c3.7,6 6,11.2 8,18.8c0.7,2.5 1.9,7 2.7,10.1c0.8,3.1 2.7,10.2 4.1,15.8l2.6,10.2l4.6,5.2c2.6,2.9 5.8,6.5 7.2,8c1.4,1.6 2.5,3 2.5,3.2c0,0.3 -34.5,29.3 -34.9,29.3C374.2,161.7 372.2,159.7 369.8,157.4zM275.9,141c-1.3,-0.6 -2.2,-1.4 -2.9,-2.3c-2.1,-2.7 -2,2.4 -1.9,-68.5l0.1,-64l0.7,-1.2c1,-1.9 2,-2.9 3.7,-3.9l1.6,-0.9l37.8,-0.1c42.5,-0.1 39.4,-0.2 42.1,2.2c0.9,0.8 1.8,2 2.2,2.9c0.7,1.6 0.7,1.6 0.8,14.2l0.1,12.6l-1.8,-0.1c-1.4,-0.1 -2.1,0 -3.2,0.5c-2,1 -3.9,2.9 -5.1,5.1l-1,2l0,-12.8l0,-12.8h-33.6h-33.6v51.3v51.3h33.6h33.6l0.1,-34.4c0.1,-33 0.1,-34.4 0.6,-32.9c0.3,0.8 1.8,4 3.4,7c5.5,10.6 5.4,9.9 5.4,47.2c0,27.6 -0.1,30 -1.7,33.1c-1.1,2.2 -2.7,3.7 -5.1,4.7l-1.7,0.7L314,141.8l-36.2,0.1L275.9,141L275.9,141zM318.3,135.9c2.9,-1.3 4.5,-3.7 4.4,-6.6c0,-4.1 -3.1,-7.2 -7.1,-7.2c-2.1,0 -3.6,0.6 -5.2,2.2c-2.2,2.2 -2.8,5.4 -1.3,8.3c0.7,1.4 2.5,3 4,3.5C314.6,136.6 317,136.6 318.3,135.9z"/>
<path
android:fillColor="#FF000000"
android:pathData="M39.4,157.4l4.3,-4.3l7.1,-2.4c3.9,-1.3 8.7,-3 10.7,-3.7l3.7,-1.3l-3.5,-0.2c-8.2,-0.4 -13,-4 -14.3,-10.9c-0.8,-4.1 -1.1,-17.3 -0.8,-33c0.2,-8.1 0.2,-15.4 0,-16.3c-0.1,-0.9 -0.5,-2.4 -0.9,-3.4c-1.2,-3.5 -0.3,-11.9 1.9,-17.6c0.3,-0.9 1.9,-4.2 3.5,-7.4c4.2,-8.2 4.5,-8.9 4.9,-10.5c0.5,-1.8 0.2,-5.4 -0.5,-6.8c-0.7,-1.3 -2.2,-2.9 -3.2,-3.5c-1.3,-0.7 -2.6,0.1 -4.7,2.9c-3.4,4.5 -14,19.4 -15.7,22.2c-3.7,6 -6,11.2 -8,18.8c-0.7,2.5 -1.9,7 -2.7,10.1c-0.8,3.1 -2.7,10.2 -4.1,15.8l-2.6,10.2l-4.6,5.2c-2.6,2.9 -5.8,6.5 -7.2,8s-2.5,3 -2.5,3.2c0,0.3 34.5,29.3 34.9,29.3C35,161.7 37.1,159.7 39.4,157.4zM133.3,141c1.3,-0.6 2.2,-1.4 2.9,-2.3c2.1,-2.7 2,2.4 1.9,-68.5l-0.1,-64l-0.7,-1.2c-1,-1.9 -2,-2.9 -3.7,-3.9l-1.6,-0.9l-37.8,-0.1c-42.5,-0.1 -39.4,-0.2 -42.1,2.2c-0.9,0.8 -1.8,2 -2.2,2.9c-0.7,1.6 -0.7,1.6 -0.8,14.2L49,32l1.8,-0.1c1.4,-0.1 2.1,0 3.2,0.5c2,1 3.9,2.9 5.1,5.1l1,2l0,-12.8l0,-12.8h33.6h33.6v51.3v51.3L93.8,116.5L60.2,116.5l-0.1,-34.4c-0.1,-33 -0.1,-34.4 -0.6,-32.9c-0.3,0.8 -1.8,4 -3.4,7c-5.5,10.6 -5.4,9.9 -5.4,47.2c0,27.6 0.1,30 1.7,33.1c1.1,2.2 2.7,3.7 5.1,4.7l1.7,0.7l36.2,0.1l36.2,0.1L133.3,141L133.3,141zM90.9,135.9c-2.9,-1.3 -4.5,-3.7 -4.4,-6.6c0,-4.1 3.1,-7.2 7.1,-7.2c2.1,0 3.6,0.6 5.2,2.2c2.2,2.2 2.8,5.4 1.3,8.3c-0.7,1.4 -2.5,3 -4,3.5C94.6,136.6 92.3,136.6 90.9,135.9z"/>
<path
android:fillColor="#FF000000"
android:pathData="M80.5,63h2.3v2.3h2.3v2.3L73.6,67.6v-2.3h2.3v-4.6h4.6L80.5,63L80.5,63zM110.5,83.8h2.3v-2.3h-2.3L110.5,83.8zM82.8,63h2.3v-2.3h-2.3L82.8,63zM115.1,83.8h2.3v-2.3h-2.3L115.1,83.8zM87.4,86.1L92,86.1L92,83.8h-4.6L87.4,86.1zM108.2,86.1L108.2,83.8h-2.3v2.3L108.2,86.1zM99,86.1h2.3v-4.6L99,81.5L99,86.1zM80.5,56.1v2.3h6.9v-2.3L80.5,56.1zM78.2,58.4v-2.3h-4.6v4.6h2.3v-2.3L78.2,58.4zM85.1,53.8L69,53.8v-16.1h16.1L85.1,53.8zM82.8,40L71.3,40v11.5h11.5L82.8,40zM73.6,81.5h6.9v-6.9h-6.9L73.6,81.5zM96.6,79.1v2.3L99,81.4v-2.3L96.6,79.1zM80.5,42.3h-6.9v6.9h6.9L80.5,42.3zM117.4,37.7L117.4,53.8L101.3,53.8v-16.1L117.4,37.7zM115.1,40L103.6,40v11.5h11.5L115.1,40zM69,69.9h16.1v16.1L69,86L69,69.9zM71.3,83.8h11.5v-11.5L71.3,72.3L71.3,83.8zM71.3,56.1L69,56.1v11.5h2.3L71.3,56.1zM101.3,67.6v2.3h2.3v-2.3L101.3,67.6zM94.3,76.9v-2.3L92,74.6v2.3h-4.6v4.6L92,81.5v2.3h2.3v-4.6h2.3v-2.3L94.3,76.9zM87.4,46.9L92,46.9v-2.3h-4.6L87.4,46.9zM105.9,65.3h4.6v2.3h2.3v-6.9h-2.3v-4.6h-2.3v6.9h-6.9v2.3h2.3v2.3h2.3L105.9,65.3zM108.2,72.2h-2.3v-2.3h-2.3v4.6h-6.9v2.3h4.6v4.6h2.3v2.3h2.3v-4.6h9.2v-2.3h-6.9L108.2,72.2zM108.2,72.2h2.3v-4.6h-2.3L108.2,72.2zM89.7,72.2v-2.3L92,69.9v-2.3h2.3v-2.3h2.3v-4.6h6.9v-4.6h-2.3v2.3L99,58.4v-9.2h-2.3v-4.6L99,44.6v-6.9h-2.3v4.6h-2.3v-4.6h-6.9v4.6h2.3v-2.3L92,40v4.6h2.3v6.9h2.3v2.3h-2.3v4.6L92,58.4L92,53.8h-2.3v-2.3h-2.3v4.6h2.3v2.3h-2.3v6.9h2.3v-4.6L92,60.7v4.6h-2.3v2.3h-2.3v6.9L92,74.5v-2.3L89.7,72.2zM115.1,74.5v-2.3h-4.6v2.3L115.1,74.5zM112.8,42.3h-6.9v6.9h6.9L112.8,42.3zM94.3,72.2L99,72.2v-2.3h-2.3v-2.3h-2.3L94.4,72.2zM99,67.6v-2.3h-2.3v2.3L99,67.6zM112.8,58.4h4.6v-2.3h-4.6L112.8,58.4zM115.1,76.9h2.3v-2.3h-2.3L115.1,76.9zM115.1,63h2.3v-2.3h-2.3L115.1,63zM94.3,51.5L92,51.5v2.3h2.3L94.3,51.5zM94.3,51.5"/>
<!-- <path-->
<!-- android:fillColor="#FF000000"-->
<!-- android:pathData="M303.5,63h2.3v2.3h2.3v2.3h-11.5v-2.3h2.3v-4.6h4.6L303.5,63L303.5,63zM333.5,83.8h2.3v-2.3h-2.3L333.5,83.8zM305.8,63h2.3v-2.3h-2.3L305.8,63zM338.1,83.8h2.3v-2.3h-2.3L338.1,83.8zM310.4,86.1h4.6L315,83.8h-4.6L310.4,86.1zM331.2,86.1L331.2,83.8h-2.3v2.3L331.2,86.1zM322,86.1h2.3v-4.6L322,81.5L322,86.1zM303.5,56.1v2.3h6.9v-2.3L303.5,56.1zM301.2,58.4v-2.3h-4.6v4.6h2.3v-2.3L301.2,58.4zM308.1,53.8L292,53.8v-16.1h16.1L308.1,53.8zM305.8,40h-11.5v11.5h11.5L305.8,40zM296.6,81.5h6.9v-6.9h-6.9L296.6,81.5zM319.6,79.1v2.3h2.3v-2.3L319.6,79.1zM303.5,42.3h-6.9v6.9h6.9L303.5,42.3zM340.4,37.7L340.4,53.8h-16.1v-16.1L340.4,37.7zM338.1,40h-11.5v11.5h11.5L338.1,40zM292,69.9h16.1v16.1L292,86L292,69.9zM294.3,83.8h11.5v-11.5h-11.5L294.3,83.8zM294.3,56.1L292,56.1v11.5h2.3L294.3,56.1zM324.3,67.6v2.3h2.3v-2.3L324.3,67.6zM317.3,76.9v-2.3L315,74.6v2.3h-4.6v4.6h4.6v2.3h2.3v-4.6h2.3v-2.3L317.3,76.9zM310.4,46.9h4.6v-2.3h-4.6L310.4,46.9zM328.9,65.3h4.6v2.3h2.3v-6.9h-2.3v-4.6h-2.3v6.9h-6.9v2.3h2.3v2.3h2.3L328.9,65.3zM331.2,72.2h-2.3v-2.3h-2.3v4.6h-6.9v2.3h4.6v4.6h2.3v2.3h2.3v-4.6h9.2v-2.3h-6.9L331.2,72.2zM331.2,72.2h2.3v-4.6h-2.3L331.2,72.2zM312.7,72.2v-2.3h2.3v-2.3h2.3v-2.3h2.3v-4.6h6.9v-4.6h-2.3v2.3L322,58.4v-9.2h-2.3v-4.6h2.3v-6.9h-2.3v4.6h-2.3v-4.6h-6.9v4.6h2.3v-2.3h2.3v4.6h2.3v6.9h2.3v2.3h-2.3v4.6L315,58.4L315,53.8h-2.3v-2.3h-2.3v4.6h2.3v2.3h-2.3v6.9h2.3v-4.6h2.3v4.6h-2.3v2.3h-2.3v6.9h4.6v-2.3L312.7,72.2zM338.1,74.5v-2.3h-4.6v2.3L338.1,74.5zM335.8,42.3h-6.9v6.9h6.9L335.8,42.3zM317.3,72.2h4.6v-2.3h-2.3v-2.3h-2.3L317.3,72.2zM322,67.6v-2.3h-2.3v2.3L322,67.6zM335.8,58.4h4.6v-2.3h-4.6L335.8,58.4zM338.1,76.9h2.3v-2.3h-2.3L338.1,76.9zM338.1,63h2.3v-2.3h-2.3L338.1,63zM317.3,51.5L315,51.5v2.3h2.3L317.3,51.5zM317.3,51.5"/>-->
<path
android:fillColor="#000000"
android:pathData="M 207.61295,88.782357 h -6.42588 v -12.85177 h 6.42588 m 0,25.703533 h -6.42588 v -6.425883 h 6.42588 m -38.5553,16.064713 h 70.68472 L 204.40001,50.227047 Z"
android:strokeWidth="3.21294165"/>
</vector>

View File

@@ -31,7 +31,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView1" app:layout_constraintTop_toBottomOf="@+id/textView1"
app:srcCompat="@drawable/qr_code_error" /> app:srcCompat="@drawable/qr_code_social_backup_error" />
<TextView <TextView
android:id="@+id/textView2" android:id="@+id/textView2"

View File

@@ -31,7 +31,20 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintTop_toBottomOf="@+id/textView"
app:srcCompat="@drawable/qr_code_intro" /> app:srcCompat="@drawable/qr_code_social_backup" />
<TextView
android:id="@+id/textViewExplain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:gravity="center"
android:text="@string/custodian_recovery_explainer_extra"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:layout_editor_absoluteX="16dp" />
<Button <Button
android:id="@+id/button" android:id="@+id/button"
@@ -42,6 +55,6 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" /> app:layout_constraintTop_toBottomOf="@+id/textViewExplain" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -31,7 +31,21 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintTop_toBottomOf="@+id/textView"
app:srcCompat="@drawable/qr_code_intro" /> app:srcCompat="@drawable/qr_code_social_backup" />
<TextView
android:id="@+id/textViewExplain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:gravity="center"
android:text="@string/recovery_explainer_extra"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:layout_editor_absoluteX="16dp" />
<Button <Button
android:id="@+id/beginButton" android:id="@+id/beginButton"
@@ -42,6 +56,6 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" /> app:layout_constraintTop_toBottomOf="@+id/textViewExplain" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<!-- <org.briarproject.briar.android.contact.add.nearby.CameraView-->
<!-- android:id="@+id/camera_view"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent" />-->
<LinearLayout
android:id="@+id/camera_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="vertical">
<LinearLayout
android:id="@+id/status_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/margin_medium"
android:visibility="visible"
tools:visibility="visible">
<!-- <ProgressBar-->
<!-- style="?android:attr/progressBarStyleLarge"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content" />-->
<TextView
android:id="@+id/connect_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="@dimen/margin_large"
android:textSize="20sp"
android:text="@string/waiting_for_contact_to_scan" />
</LinearLayout>
<org.briarproject.briar.android.view.QrCodeView
android:id="@+id/qr_code_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/white"
tools:visibility="visible" />
</LinearLayout>
</FrameLayout>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/margin_large"
android:paddingTop="@dimen/margin_medium"
android:paddingRight="@dimen/margin_large"
android:paddingBottom="@dimen/margin_medium">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/recovery_account_recovered"
android:textSize="30sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="32dp"
android:layout_marginLeft="32dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="32dp"
android:layout_marginRight="32dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView1"
app:srcCompat="@drawable/ic_baseline_done_outline_24" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/continue_button"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/recovery_account_recovered_explain"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView2" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -676,19 +676,23 @@
<!-- recovery from the secret owner's POV --> <!-- recovery from the secret owner's POV -->
<string name="recovery_explainer">You need to meet your trusted contacts in-person to receive pieces</string> <string name="recovery_explainer">You need to meet your trusted contacts in-person to receive pieces</string>
<string name="recovery_explainer_extra">Your trusted contact must scan a QR code to initiate the transfer.\n\nYou must both be connected to the same wifi network</string>
<string name="recovery_begin">Begin</string> <string name="recovery_begin">Begin</string>
<string name="recovery_failed_to_receive">Failed to receive backup piece</string> <string name="recovery_failed_to_receive">Failed to receive backup piece</string>
<string name="recovery_helpful_suggestions">Please check that bluetooth is swtiched on and that no-one but your trusted contact is able to scan the QR code</string> <string name="recovery_helpful_suggestions">Please check that you are both connected to the same wifi network and try again</string>
<string name="recovery_retry">Retry</string> <string name="recovery_retry">Retry</string>
<string name="recovery_recovered_shards">Recovered backup pieces:</string> <string name="recovery_recovered_shards">Recovered backup pieces:</string>
<string name="recovery_scan_qr_code">Show QR code</string> <string name="recovery_scan_qr_code">Show QR code</string>
<string name="recovery_recovering_account">Recovering account…</string> <string name="recovery_recovering_account">Recovering account…</string>
<string name="recovery_shard_received">Account backup piece received</string> <string name="recovery_shard_received">Account backup piece received</string>
<string name="recovery_account_recovered">Account recovered</string> <string name="recovery_account_recovered">Account recovered</string>
<string name="recovery_account_recovered_explain">You must now set a new password for your account</string>
<string name="recovery_sending_ack">Sending acknowledgement</string>
<!-- recovery from the custodian's POV --> <!-- recovery from the custodian's POV -->
<string name="custodian_recovery_explainer">You need to meet in-person to transfer backup piece</string> <string name="custodian_recovery_explainer">You need to meet in-person to transfer backup piece</string>
<string name="custodian_recovery_explainer_extra">Make sure you are both connected to the same wifi network, and scan the QR code on your contact\'s device to begin transferring the backup piece</string>
<string name="custodian_recovery_failed_to_send">Failed to send backup piece</string> <string name="custodian_recovery_failed_to_send">Failed to send backup piece</string>
<string name="custodian_scan_code">Scan code</string> <string name="custodian_scan_code">Scan code</string>
<string name="custodian_shard_sent">Account backup piece transmitted</string> <string name="custodian_shard_sent">Account backup piece transmitted</string>
@@ -716,7 +720,7 @@
<!-- activity names --> <!-- activity names -->
<string name="activity_name_distributed_backup">Social Backup</string> <string name="activity_name_distributed_backup">Social Backup</string>
<string name="activity_name_old_distributed_backup">Old Social Backup</string> <string name="activity_name_restore_account">Restore Account</string>
<!-- conversation --> <!-- conversation -->
<string name="social_backup_shard_received">You have received a social backup shard.</string> <string name="social_backup_shard_received">You have received a social backup shard.</string>

View File

@@ -1,10 +1,9 @@
package org.briarproject.briar.socialbackup; package org.briarproject.briar.api.socialbackup;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.briar.api.socialbackup.Shard;
import java.util.Map; import java.util.Map;
@@ -13,14 +12,14 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class ContactData { public class ContactData {
private final Contact contact; private final Contact contact;
private final Map<TransportId, TransportProperties> properties; private final Map<TransportId, TransportProperties> properties;
@Nullable @Nullable
private final Shard shard; private final Shard shard;
ContactData(Contact contact, public ContactData(Contact contact,
Map<TransportId, TransportProperties> properties, Map<TransportId, TransportProperties> properties,
@Nullable Shard shard) { @Nullable Shard shard) {
this.contact = contact; this.contact = contact;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.socialbackup; package org.briarproject.briar.api.socialbackup;
import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.Identity;
@@ -9,7 +9,7 @@ public class SocialBackup {
private List<ContactData> contacts; private List<ContactData> contacts;
private int version; private int version;
SocialBackup (Identity identity, List<ContactData> contacts, int version) { public SocialBackup (Identity identity, List<ContactData> contacts, int version) {
this.identity = identity; this.identity = identity;
this.contacts = contacts; this.contacts = contacts;
this.version = version; this.version = version;

View File

@@ -0,0 +1,24 @@
package org.briarproject.briar.api.socialbackup.recovery;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
import org.briarproject.briar.api.socialbackup.SocialBackup;
import java.security.GeneralSecurityException;
public interface RestoreAccount {
int getNumberOfShards();
boolean addReturnShardPayload(ReturnShardPayload toAdd);
boolean canRecover();
int recover() throws FormatException, GeneralSecurityException;
SocialBackup getSocialBackup();
void addContactsToDb() throws DbException;
}

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
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.BackupPayload; import org.briarproject.briar.api.socialbackup.BackupPayload;
import org.briarproject.briar.api.socialbackup.SocialBackup;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;

View File

@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.briar.api.socialbackup.BackupPayload; import org.briarproject.briar.api.socialbackup.BackupPayload;
import org.briarproject.briar.api.socialbackup.MessageParser; import org.briarproject.briar.api.socialbackup.MessageParser;
import org.briarproject.briar.api.socialbackup.Shard; import org.briarproject.briar.api.socialbackup.Shard;
import org.briarproject.briar.api.socialbackup.SocialBackup;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.SecureRandom; import java.security.SecureRandom;
@@ -54,7 +55,7 @@ public class BackupPayloadDecoderImpl implements BackupPayloadDecoder {
this.messageParser = messageParser; this.messageParser = messageParser;
} }
public SocialBackup decodeBackupPayload( public org.briarproject.briar.api.socialbackup.SocialBackup decodeBackupPayload(
SecretKey secret, SecretKey secret,
BackupPayload backupPayload) BackupPayload backupPayload)
throws FormatException, GeneralSecurityException { throws FormatException, GeneralSecurityException {
@@ -103,7 +104,7 @@ public class BackupPayloadDecoderImpl implements BackupPayloadDecoder {
handShakePrivateKey, created); handShakePrivateKey, created);
LOG.info("New identity created"); LOG.info("New identity created");
List<ContactData> contactDataList = new ArrayList(); List<org.briarproject.briar.api.socialbackup.ContactData> contactDataList = new ArrayList();
for (int i = 0; i < bdfContactData.size(); i++) { for (int i = 0; i < bdfContactData.size(); i++) {
BdfList bdfData = bdfContactData.getList(i); BdfList bdfData = bdfContactData.getList(i);
@@ -139,8 +140,8 @@ public class BackupPayloadDecoderImpl implements BackupPayloadDecoder {
Contact contact = Contact contact =
new Contact(contactId, author, author.getId(), alias, new Contact(contactId, author, author.getId(), alias,
contactHandshakePublicKey, false); contactHandshakePublicKey, false);
ContactData contactData = org.briarproject.briar.api.socialbackup.ContactData contactData =
new ContactData(contact, properties, shard); new org.briarproject.briar.api.socialbackup.ContactData(contact, properties, shard);
contactDataList.add(contactData); contactDataList.add(contactData);
LOG.info("Contact added"); LOG.info("Contact added");
} }

View File

@@ -3,12 +3,14 @@ package org.briarproject.briar.socialbackup;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.socialbackup.BackupPayload;
import org.briarproject.briar.api.socialbackup.ContactData;
import java.util.List; import java.util.List;
@NotNullByDefault @NotNullByDefault
interface BackupPayloadEncoder { interface BackupPayloadEncoder {
org.briarproject.briar.api.socialbackup.BackupPayload encodeBackupPayload(SecretKey secret, Identity identity, BackupPayload encodeBackupPayload(SecretKey secret, Identity identity,
List<ContactData> contactData, int version); List<ContactData> contactData, int version);
} }

View File

@@ -46,7 +46,7 @@ class BackupPayloadEncoderImpl implements BackupPayloadEncoder {
@Override @Override
public org.briarproject.briar.api.socialbackup.BackupPayload encodeBackupPayload(SecretKey secret, public org.briarproject.briar.api.socialbackup.BackupPayload encodeBackupPayload(SecretKey secret,
Identity identity, List<ContactData> contactData, int version) { Identity identity, List<org.briarproject.briar.api.socialbackup.ContactData> contactData, int version) {
// Encode the local identity // Encode the local identity
BdfList bdfIdentity = new BdfList(); BdfList bdfIdentity = new BdfList();
LocalAuthor localAuthor = identity.getLocalAuthor(); LocalAuthor localAuthor = identity.getLocalAuthor();
@@ -56,7 +56,7 @@ class BackupPayloadEncoderImpl implements BackupPayloadEncoder {
bdfIdentity.add(identity.getHandshakePrivateKey().getEncoded()); bdfIdentity.add(identity.getHandshakePrivateKey().getEncoded());
// Encode the contact data // Encode the contact data
BdfList bdfContactData = new BdfList(); BdfList bdfContactData = new BdfList();
for (ContactData cd : contactData) { for (org.briarproject.briar.api.socialbackup.ContactData cd : contactData) {
BdfList bdfData = new BdfList(); BdfList bdfData = new BdfList();
Contact contact = cd.getContact(); Contact contact = cd.getContact();
bdfData.add(clientHelper.toList(contact.getAuthor())); bdfData.add(clientHelper.toList(contact.getAuthor()));

View File

@@ -52,6 +52,7 @@ import org.briarproject.briar.api.socialbackup.ShardMessageHeader;
import org.briarproject.briar.api.socialbackup.ShardReceivedEvent; import org.briarproject.briar.api.socialbackup.ShardReceivedEvent;
import org.briarproject.briar.api.socialbackup.SocialBackupManager; import org.briarproject.briar.api.socialbackup.SocialBackupManager;
import org.briarproject.briar.client.ConversationClientImpl; import org.briarproject.briar.client.ConversationClientImpl;
import org.briarproject.briar.api.socialbackup.ContactData;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -278,7 +279,7 @@ class SocialBackupManagerImpl extends ConversationClientImpl
} }
// Create the encrypted backup payload // Create the encrypted backup payload
SecretKey secret = crypto.generateSecretKey(); SecretKey secret = crypto.generateSecretKey();
List<ContactData> contactData = loadContactData(txn); List<org.briarproject.briar.api.socialbackup.ContactData> contactData = loadContactData(txn);
BackupPayload payload = BackupPayload payload =
createBackupPayload(txn, secret, contactData, 0); createBackupPayload(txn, secret, contactData, 0);
// Create the shards // Create the shards
@@ -415,17 +416,17 @@ class SocialBackupManagerImpl extends ConversationClientImpl
} }
private BackupPayload createBackupPayload(Transaction txn, private BackupPayload createBackupPayload(Transaction txn,
SecretKey secret, List<ContactData> contactData, int version) SecretKey secret, List<org.briarproject.briar.api.socialbackup.ContactData> contactData, int version)
throws DbException { throws DbException {
Identity identity = identityManager.getIdentity(txn); Identity identity = identityManager.getIdentity(txn);
return backupPayloadEncoder.encodeBackupPayload(secret, identity, return backupPayloadEncoder.encodeBackupPayload(secret, identity,
contactData, version); contactData, version);
} }
private List<ContactData> loadContactData(Transaction txn) private List<org.briarproject.briar.api.socialbackup.ContactData> loadContactData(Transaction txn)
throws DbException { throws DbException {
Collection<Contact> contacts = contactManager.getContacts(txn); Collection<Contact> contacts = contactManager.getContacts(txn);
List<ContactData> contactData = new ArrayList<>(); List<org.briarproject.briar.api.socialbackup.ContactData> contactData = new ArrayList<>();
for (Contact c : contacts) { for (Contact c : contacts) {
// Skip contacts that are in the process of being removed // Skip contacts that are in the process of being removed
Group contactGroup = getContactGroup(c); Group contactGroup = getContactGroup(c);
@@ -433,7 +434,7 @@ class SocialBackupManagerImpl extends ConversationClientImpl
Map<TransportId, TransportProperties> props = Map<TransportId, TransportProperties> props =
getTransportProperties(txn, c.getId()); getTransportProperties(txn, c.getId());
Shard shard = getRemoteShard(txn, contactGroup.getId()); Shard shard = getRemoteShard(txn, contactGroup.getId());
contactData.add(new ContactData(c, props, shard)); contactData.add(new org.briarproject.briar.api.socialbackup.ContactData(c, props, shard));
} }
return contactData; return contactData;
} }
@@ -513,7 +514,7 @@ class SocialBackupManagerImpl extends ConversationClientImpl
throw new DbException(e); throw new DbException(e);
} }
} }
private void updateBackup(Transaction txn, List<ContactData> contactData) private void updateBackup(Transaction txn, List<org.briarproject.briar.api.socialbackup.ContactData> contactData)
throws DbException { throws DbException {
BackupMetadata backupMetadata = requireNonNull(getBackupMetadata(txn)); BackupMetadata backupMetadata = requireNonNull(getBackupMetadata(txn));
int newVersion = backupMetadata.getVersion() + 1; int newVersion = backupMetadata.getVersion() + 1;

View File

@@ -11,8 +11,10 @@ import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.socialbackup.SocialBackupExchangeManager; import org.briarproject.briar.api.socialbackup.SocialBackupExchangeManager;
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 org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask; import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
import org.briarproject.briar.socialbackup.recovery.CustodianTaskImpl; import org.briarproject.briar.socialbackup.recovery.CustodianTaskImpl;
import org.briarproject.briar.socialbackup.recovery.RestoreAccountImpl;
import org.briarproject.briar.socialbackup.recovery.SecretOwnerTaskImpl; import org.briarproject.briar.socialbackup.recovery.SecretOwnerTaskImpl;
import javax.inject.Inject; import javax.inject.Inject;
@@ -119,4 +121,10 @@ public class SocialBackupModule {
CustodianTask custodianTask(CustodianTaskImpl custodianTask) { CustodianTask custodianTask(CustodianTaskImpl custodianTask) {
return custodianTask; return custodianTask;
} }
@Provides
@Singleton
RestoreAccount restoreAccount(RestoreAccountImpl restoreAccount) {
return restoreAccount;
}
} }

View File

@@ -0,0 +1,136 @@
package org.briarproject.briar.socialbackup.recovery;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
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.briar.api.socialbackup.BackupPayload;
import org.briarproject.briar.api.socialbackup.ContactData;
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.SocialBackup;
import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
import org.briarproject.briar.socialbackup.BackupPayloadDecoder;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Logger.getLogger;
public class RestoreAccountImpl implements RestoreAccount {
private final ArrayList<ReturnShardPayload> recoveredShards = new ArrayList<>();
private final DarkCrystal darkCrystal;
private final Executor ioExecutor;
private final DatabaseComponent db;
private final ContactManager contactManager;
private final LifecycleManager lifecycleManager;
private SecretKey secretKey;
private final BackupPayloadDecoder backupPayloadDecoder;
private SocialBackup socialBackup;
private static final Logger LOG =
getLogger(RestoreAccountImpl.class.getName());
@Inject
RestoreAccountImpl(DarkCrystal darkCrystal,
BackupPayloadDecoder backupPayloadDecoder, DatabaseComponent db,
@IoExecutor Executor ioExecutor,
ContactManager contactManager,
LifecycleManager lifecycleManager) {
this.darkCrystal = darkCrystal;
this.backupPayloadDecoder = backupPayloadDecoder;
this.db = db;
this.ioExecutor = ioExecutor;
this.lifecycleManager = lifecycleManager;
this.contactManager = contactManager;
}
public int getNumberOfShards() {
return recoveredShards.size();
}
// TODO figure out how to actually use a hash set for these objects
public boolean addReturnShardPayload(ReturnShardPayload toAdd) {
boolean found = false;
for (ReturnShardPayload returnShardPayload : recoveredShards) {
if (toAdd.equals(returnShardPayload)) {
found = true;
break;
}
}
if (!found) recoveredShards.add(toAdd);
return !found;
}
public boolean canRecover() {
ArrayList<Shard> shards = new ArrayList<>();
for (ReturnShardPayload returnShardPayload : recoveredShards) {
// TODO check shards all have same secret id
shards.add(returnShardPayload.getShard());
}
try {
secretKey = darkCrystal.combineShards(shards);
} catch (GeneralSecurityException e) {
// TODO handle error message
return false;
}
return true;
}
public int recover() throws FormatException, GeneralSecurityException {
if (secretKey == null) throw new GeneralSecurityException();
// Find backup with highest version number
int highestVersion = -1;
for (ReturnShardPayload returnShardPayload : recoveredShards) {
BackupPayload backupPayload = returnShardPayload.getBackupPayload();
SocialBackup s = backupPayloadDecoder
.decodeBackupPayload(secretKey, backupPayload);
if (s.getVersion() > highestVersion) {
socialBackup = s;
highestVersion = s.getVersion();
}
}
return highestVersion;
}
public SocialBackup getSocialBackup() {
return socialBackup;
}
public void addContactsToDb() throws DbException {
if (socialBackup == null) throw new DbException();
AuthorId localAuthorId = socialBackup.getIdentity().getId();
ioExecutor.execute(() -> {
try {
lifecycleManager.waitForDatabase();
} catch (InterruptedException e) {
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());
}
});
} catch (DbException e) {
LOG.warning("Error adding contacts to database");
LOG.warning(e.getMessage());
}
LOG.info("Added all contacts");
});
}
}

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.briar.api.socialbackup.BackupPayload; import org.briarproject.briar.api.socialbackup.BackupPayload;
import org.briarproject.briar.api.socialbackup.MessageEncoder;
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.Shard;
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask; import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
@@ -15,9 +16,9 @@ import org.junit.Test;
import java.io.File; import java.io.File;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.Arrays; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static junit.framework.TestCase.fail; import static junit.framework.TestCase.fail;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
@@ -32,6 +33,8 @@ public class ReturnShardIntegrationTest extends BrambleTestCase {
private ReturnShardIntegrationTestComponent owner, custodian; private ReturnShardIntegrationTestComponent owner, custodian;
private ReturnShardPayload remotePayload;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
assertTrue(testDir.mkdirs()); assertTrue(testDir.mkdirs());
@@ -50,16 +53,20 @@ public class ReturnShardIntegrationTest extends BrambleTestCase {
} }
@Test @Test
public void testReturnShard() { public void testReturnShard() throws Exception {
SecretOwnerTask secretOwnerTask = owner.getSecretOwnerTask(); SecretOwnerTask secretOwnerTask = owner.getSecretOwnerTask();
CustodianTask custodianTask = custodian.getCustodianTask(); CustodianTask custodianTask = custodian.getCustodianTask();
byte[] payload = "its nice to be important but its more important to be nice".getBytes(); MessageEncoder messageEncoder = owner.getMessageEncoder();
Shard shard = new Shard("secretid".getBytes(), "shard".getBytes()); CountDownLatch secretOwnerFinished = new CountDownLatch(1);
CountDownLatch custodianFinished = new CountDownLatch(1);
Shard shard = new Shard("secret id".getBytes(), "shard".getBytes());
BackupPayload backupPayload = new BackupPayload("backup payload".getBytes()); BackupPayload backupPayload = new BackupPayload("backup payload".getBytes());
ReturnShardPayload returnShardPayload = new ReturnShardPayload(shard, backupPayload); ReturnShardPayload returnShardPayload = new ReturnShardPayload(shard, backupPayload);
// payloadBytes = clientHelper byte[] payloadBytes = messageEncoder.encodeReturnShardPayload(returnShardPayload);
SecretOwnerTask.Observer ownerObserver = SecretOwnerTask.Observer ownerObserver =
state -> { state -> {
@@ -67,30 +74,21 @@ public class ReturnShardIntegrationTest extends BrambleTestCase {
SecretOwnerTask.State.Listening listening = SecretOwnerTask.State.Listening listening =
(SecretOwnerTask.State.Listening) state; (SecretOwnerTask.State.Listening) state;
byte[] qrPayload = listening.getLocalPayload(); byte[] qrPayload = listening.getLocalPayload();
System.out.println(qrPayload.length);
transferQrCode(custodianTask, qrPayload); transferQrCode(custodianTask, qrPayload);
} else if (state instanceof SecretOwnerTask.State.Success) { } else if (state instanceof SecretOwnerTask.State.Success) {
ReturnShardPayload remotePayload = ((SecretOwnerTask.State.Success) state).getRemotePayload(); remotePayload = ((SecretOwnerTask.State.Success) state).getRemotePayload();
assertTrue(remotePayload.equals(payload)); secretOwnerFinished.countDown();
System.out.println("Success");
} else if (state instanceof SecretOwnerTask.State.Failure) { } else if (state instanceof SecretOwnerTask.State.Failure) {
System.out.println("Owner state: failure");
fail(); fail();
} else {
System.out.println(
"owner: " + state.getClass().getSimpleName());
} }
}; };
CustodianTask.Observer custodianObserver = CustodianTask.Observer custodianObserver =
state -> { state -> {
if (state instanceof CustodianTask.State.Success) { if (state instanceof CustodianTask.State.Success) {
assertEquals(1, 1); custodianFinished.countDown();
} else if (state instanceof CustodianTask.State.Failure) { } else if (state instanceof CustodianTask.State.Failure) {
fail(); fail();
} else {
System.out.println(
"custodian: " + state.getClass().getSimpleName());
} }
}; };
@@ -105,19 +103,14 @@ public class ReturnShardIntegrationTest extends BrambleTestCase {
custodian.getIoExecutor().execute(() -> { custodian.getIoExecutor().execute(() -> {
try { try {
custodianTask.start(custodianObserver, payload); custodianTask.start(custodianObserver, payloadBytes);
} catch (Exception e) { } catch (Exception e) {
fail(); fail();
} }
}); });
assertTrue(secretOwnerFinished.await(15000, MILLISECONDS));
// TODO how to get the test to wait for the io to finish assertTrue(custodianFinished.await(15000, MILLISECONDS));
try { assertTrue(remotePayload.equals(returnShardPayload));
// Thread.sleep(1000);
tearDown();
} catch (Exception e) {
fail();
}
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.socialbackup.recovery;
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons; import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
@@ -10,6 +11,7 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule; import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
import org.briarproject.briar.api.socialbackup.MessageEncoder;
import org.briarproject.briar.api.socialbackup.recovery.CustodianTask; import org.briarproject.briar.api.socialbackup.recovery.CustodianTask;
import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask; import org.briarproject.briar.api.socialbackup.recovery.SecretOwnerTask;
import org.briarproject.briar.socialbackup.SocialBackupModule; import org.briarproject.briar.socialbackup.SocialBackupModule;
@@ -44,6 +46,8 @@ interface ReturnShardIntegrationTestComponent
LifecycleManager getLifecycleManager(); LifecycleManager getLifecycleManager();
MessageEncoder getMessageEncoder();
SecretOwnerTask getSecretOwnerTask(); SecretOwnerTask getSecretOwnerTask();
CustodianTask getCustodianTask(); CustodianTask getCustodianTask();