mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Initial implementation of social backup client.
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
package org.briarproject.bramble.api.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
@NotNullByDefault
|
||||
interface AuthenticatedCipher {
|
||||
public interface AuthenticatedCipher {
|
||||
|
||||
/**
|
||||
* Initializes this cipher for encryption or decryption with a key and an
|
||||
@@ -26,6 +26,11 @@ public interface IdentityManager {
|
||||
*/
|
||||
void registerIdentity(Identity i);
|
||||
|
||||
/**
|
||||
* Returns the cached local identity or loads it from the database.
|
||||
*/
|
||||
Identity getIdentity(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the cached local identity or loads it from the database.
|
||||
*/
|
||||
|
||||
@@ -74,6 +74,13 @@ public interface TransportPropertyManager {
|
||||
TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the remote transport properties for the given contact and
|
||||
* transport.
|
||||
*/
|
||||
TransportProperties getRemoteProperties(Transaction txn, ContactId c,
|
||||
TransportId t) throws DbException;
|
||||
|
||||
/**
|
||||
* Merges the given properties with the existing local properties for the
|
||||
* given transport.
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.i2p.crypto.eddsa.KeyPairGenerator;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.DecryptionException;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
||||
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.StreamDecrypter;
|
||||
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.StreamDecrypter;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.StreamEncrypter;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.StreamEncrypter;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.DataLengthException;
|
||||
|
||||
@@ -118,6 +118,11 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
|
||||
return cached.getLocalAuthor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identity getIdentity(Transaction txn) throws DbException {
|
||||
return getCachedIdentity(txn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalAuthor getLocalAuthor(Transaction txn) throws DbException {
|
||||
return getCachedIdentity(txn).getLocalAuthor();
|
||||
|
||||
@@ -294,7 +294,13 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
||||
throws DbException {
|
||||
return db.transactionWithResult(true, txn ->
|
||||
getRemoteProperties(txn, db.getContact(txn, c), t));
|
||||
getRemoteProperties(txn, c, t));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getRemoteProperties(Transaction txn,
|
||||
ContactId c, TransportId t) throws DbException {
|
||||
return getRemoteProperties(txn, db.getContact(txn, c), t);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.briar.api.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
/**
|
||||
* Thrown when an attempt is made to create a social account backup but a
|
||||
* backup already exists. This exception may occur due to concurrent updates
|
||||
* and does not indicate a database error.
|
||||
*/
|
||||
public class BackupExistsException extends DbException {
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.briarproject.briar.api.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class BackupMetadata {
|
||||
|
||||
private final SecretKey secret;
|
||||
private final List<Author> custodians;
|
||||
private final int threshold, version;
|
||||
|
||||
public BackupMetadata(SecretKey secret, List<Author> custodians,
|
||||
int threshold, int version) {
|
||||
this.secret = secret;
|
||||
this.custodians = custodians;
|
||||
this.threshold = threshold;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public SecretKey getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public List<Author> getCustodians() {
|
||||
return custodians;
|
||||
}
|
||||
|
||||
public int getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.briarproject.briar.api.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class Shard {
|
||||
|
||||
private final byte[] secretId, shard;
|
||||
private final int numShards, threshold;
|
||||
|
||||
public Shard(byte[] secretId, int numShards, int threshold,
|
||||
byte[] shard) {
|
||||
this.secretId = secretId;
|
||||
this.numShards = numShards;
|
||||
this.threshold = threshold;
|
||||
this.shard = shard;
|
||||
}
|
||||
|
||||
public byte[] getSecretId() {
|
||||
return secretId;
|
||||
}
|
||||
|
||||
public int getNumShards() {
|
||||
return numShards;
|
||||
}
|
||||
|
||||
public int getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public byte[] getShard() {
|
||||
return shard;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.briarproject.briar.api.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface SocialBackupManager {
|
||||
|
||||
/**
|
||||
* The unique ID of the social backup client.
|
||||
*/
|
||||
ClientId CLIENT_ID = new ClientId("pw.darkcrystal.backup");
|
||||
|
||||
/**
|
||||
* The current major version of the social backup client.
|
||||
*/
|
||||
int MAJOR_VERSION = 0;
|
||||
|
||||
/**
|
||||
* The current minor version of the social backup client.
|
||||
*/
|
||||
int MINOR_VERSION = 0;
|
||||
|
||||
/**
|
||||
* Returns the metadata for this device's backup, or null if no backup has
|
||||
* been created.
|
||||
*/
|
||||
@Nullable
|
||||
BackupMetadata getBackupMetadata(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Creates a backup for this device using the given custodians and
|
||||
* threshold. The encrypted backup and a shard of the backup key will be
|
||||
* sent to each custodian.
|
||||
*
|
||||
* @throws BackupExistsException If a backup already exists
|
||||
*/
|
||||
void createBackup(Transaction txn, List<ContactId> custodianIds,
|
||||
int threshold) throws DbException;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.briarproject.briar.messaging.MessagingModule;
|
||||
import org.briarproject.briar.privategroup.PrivateGroupModule;
|
||||
import org.briarproject.briar.privategroup.invitation.GroupInvitationModule;
|
||||
import org.briarproject.briar.sharing.SharingModule;
|
||||
import org.briarproject.briar.socialbackup.SocialBackupModule;
|
||||
|
||||
public interface BriarCoreEagerSingletons {
|
||||
|
||||
@@ -33,6 +34,8 @@ public interface BriarCoreEagerSingletons {
|
||||
|
||||
void inject(SharingModule.EagerSingletons init);
|
||||
|
||||
void inject(SocialBackupModule.EagerSingletons init);
|
||||
|
||||
class Helper {
|
||||
|
||||
public static void injectEagerSingletons(BriarCoreEagerSingletons c) {
|
||||
@@ -46,6 +49,7 @@ public interface BriarCoreEagerSingletons {
|
||||
c.inject(new SharingModule.EagerSingletons());
|
||||
c.inject(new IdentityModule.EagerSingletons());
|
||||
c.inject(new IntroductionModule.EagerSingletons());
|
||||
c.inject(new SocialBackupModule.EagerSingletons());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.briarproject.briar.messaging.MessagingModule;
|
||||
import org.briarproject.briar.privategroup.PrivateGroupModule;
|
||||
import org.briarproject.briar.privategroup.invitation.GroupInvitationModule;
|
||||
import org.briarproject.briar.sharing.SharingModule;
|
||||
import org.briarproject.briar.socialbackup.SocialBackupModule;
|
||||
import org.briarproject.briar.test.TestModule;
|
||||
|
||||
import dagger.Module;
|
||||
@@ -31,6 +32,7 @@ import dagger.Module;
|
||||
MessagingModule.class,
|
||||
PrivateGroupModule.class,
|
||||
SharingModule.class,
|
||||
SocialBackupModule.class,
|
||||
TestModule.class
|
||||
})
|
||||
public class BriarCoreModule {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.socialbackup.BackupMetadata;
|
||||
|
||||
@NotNullByDefault
|
||||
interface BackupMetadataEncoder {
|
||||
|
||||
BdfDictionary encodeBackupMetadata(BackupMetadata backupMetadata);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.socialbackup.BackupMetadata;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.GROUP_KEY_CUSTODIANS;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.GROUP_KEY_SECRET;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.GROUP_KEY_THRESHOLD;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.GROUP_KEY_VERSION;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class BackupMetadataEncoderImpl implements BackupMetadataEncoder {
|
||||
|
||||
private final ClientHelper clientHelper;
|
||||
|
||||
@Inject
|
||||
BackupMetadataEncoderImpl(ClientHelper clientHelper) {
|
||||
this.clientHelper = clientHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary encodeBackupMetadata(BackupMetadata backupMetadata) {
|
||||
BdfList custodians = new BdfList();
|
||||
for (Author custodian : backupMetadata.getCustodians()) {
|
||||
custodians.add(clientHelper.toList(custodian));
|
||||
}
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(GROUP_KEY_SECRET, backupMetadata.getSecret().getBytes());
|
||||
meta.put(GROUP_KEY_CUSTODIANS, custodians);
|
||||
meta.put(GROUP_KEY_THRESHOLD, backupMetadata.getThreshold());
|
||||
meta.put(GROUP_KEY_VERSION, backupMetadata.getVersion());
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.socialbackup.BackupMetadata;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
interface BackupMetadataParser {
|
||||
|
||||
@Nullable
|
||||
BackupMetadata parseBackupMetadata(BdfDictionary meta)
|
||||
throws FormatException;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.socialbackup.BackupMetadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.GROUP_KEY_CUSTODIANS;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.GROUP_KEY_SECRET;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.GROUP_KEY_THRESHOLD;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.GROUP_KEY_VERSION;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class BackupMetadataParserImpl implements BackupMetadataParser {
|
||||
|
||||
private final ClientHelper clientHelper;
|
||||
|
||||
@Inject
|
||||
BackupMetadataParserImpl(ClientHelper clientHelper) {
|
||||
this.clientHelper = clientHelper;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BackupMetadata parseBackupMetadata(BdfDictionary meta)
|
||||
throws FormatException {
|
||||
if (meta.isEmpty()) return null;
|
||||
SecretKey secret = new SecretKey(meta.getRaw(GROUP_KEY_SECRET));
|
||||
BdfList bdfCustodians = meta.getList(GROUP_KEY_CUSTODIANS);
|
||||
List<Author> custodians = new ArrayList<>(bdfCustodians.size());
|
||||
for (int i = 0; i < bdfCustodians.size(); i++) {
|
||||
BdfList author = bdfCustodians.getList(i);
|
||||
custodians.add(clientHelper.parseAndValidateAuthor(author));
|
||||
}
|
||||
int threshold = meta.getLong(GROUP_KEY_THRESHOLD).intValue();
|
||||
int version = meta.getLong(GROUP_KEY_VERSION).intValue();
|
||||
return new BackupMetadata(secret, custodians, threshold, version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class BackupPayload extends Bytes {
|
||||
|
||||
BackupPayload(byte[] payload) {
|
||||
super(payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.identity.Identity;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@NotNullByDefault
|
||||
interface BackupPayloadEncoder {
|
||||
|
||||
BackupPayload encodeBackupPayload(SecretKey secret, Identity identity,
|
||||
List<Contact> contacts,
|
||||
List<Map<TransportId, TransportProperties>> properties,
|
||||
int version);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.identity.Identity;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.AUTH_TAG_BYTES;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.NONCE_BYTES;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class BackupPayloadEncoderImpl implements BackupPayloadEncoder {
|
||||
|
||||
private final ClientHelper clientHelper;
|
||||
private final Provider<AuthenticatedCipher> cipherProvider;
|
||||
private final SecureRandom secureRandom;
|
||||
|
||||
@Inject
|
||||
BackupPayloadEncoderImpl(ClientHelper clientHelper,
|
||||
Provider<AuthenticatedCipher> cipherProvider,
|
||||
SecureRandom secureRandom) {
|
||||
this.clientHelper = clientHelper;
|
||||
this.cipherProvider = cipherProvider;
|
||||
this.secureRandom = secureRandom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BackupPayload encodeBackupPayload(SecretKey secret,
|
||||
Identity identity, List<Contact> contacts,
|
||||
List<Map<TransportId, TransportProperties>> properties,
|
||||
int version) {
|
||||
if (contacts.size() != properties.size()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
// Encode the local identity
|
||||
BdfList identityData = new BdfList();
|
||||
LocalAuthor localAuthor = identity.getLocalAuthor();
|
||||
identityData.add(clientHelper.toList(localAuthor));
|
||||
identityData.add(localAuthor.getPrivateKey().getEncoded());
|
||||
identityData.add(identity.getHandshakePublicKey().getEncoded());
|
||||
identityData.add(identity.getHandshakePrivateKey().getEncoded());
|
||||
// Encode the contacts
|
||||
BdfList contactData = new BdfList();
|
||||
for (int i = 0; i < contacts.size(); i++) {
|
||||
Contact contact = contacts.get(i);
|
||||
Map<TransportId, TransportProperties> props = properties.get(i);
|
||||
BdfList data = new BdfList();
|
||||
data.add(clientHelper.toList(contact.getAuthor()));
|
||||
data.add(contact.getAlias());
|
||||
PublicKey pub = requireNonNull(contact.getHandshakePublicKey());
|
||||
data.add(pub.getEncoded());
|
||||
data.add(clientHelper.toDictionary(props));
|
||||
contactData.add(data);
|
||||
}
|
||||
// Encode and encrypt the payload
|
||||
BdfList backup = new BdfList();
|
||||
backup.add(version);
|
||||
backup.add(identityData);
|
||||
backup.add(contactData);
|
||||
try {
|
||||
byte[] plaintext = clientHelper.toByteArray(backup);
|
||||
byte[] ciphertext = new byte[plaintext.length + AUTH_TAG_BYTES];
|
||||
byte[] nonce = new byte[NONCE_BYTES];
|
||||
secureRandom.nextBytes(nonce);
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
cipher.init(true, secret, nonce);
|
||||
int encrypted = cipher.process(plaintext, 0, plaintext.length,
|
||||
ciphertext, 0);
|
||||
if (encrypted != ciphertext.length) throw new AssertionError();
|
||||
return new BackupPayload(ciphertext);
|
||||
} catch (FormatException | GeneralSecurityException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.socialbackup.Shard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@NotNullByDefault
|
||||
interface DarkCrystal {
|
||||
|
||||
List<Shard> createShards(SecretKey secret, int shards, int threshold);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.socialbackup.Shard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.SECRET_ID_BYTES;
|
||||
|
||||
@NotNullByDefault
|
||||
class DarkCrystalStub implements DarkCrystal {
|
||||
|
||||
@Inject
|
||||
DarkCrystalStub() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Shard> createShards(SecretKey secret, int numShards,
|
||||
int threshold) {
|
||||
Random random = new Random();
|
||||
byte[] secretId = new byte[SECRET_ID_BYTES];
|
||||
random.nextBytes(secretId);
|
||||
List<Shard> shards = new ArrayList<>(numShards);
|
||||
for (int i = 0; i < numShards; i++) {
|
||||
byte[] shard = new byte[123];
|
||||
random.nextBytes(shard);
|
||||
shards.add(new Shard(secretId, numShards, threshold, shard));
|
||||
}
|
||||
return shards;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.socialbackup.Shard;
|
||||
|
||||
@NotNullByDefault
|
||||
interface MessageEncoder {
|
||||
|
||||
byte[] encodeShardMessage(Shard shard);
|
||||
|
||||
byte[] encodeBackupMessage(int version, BackupPayload payload);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.socialbackup.Shard;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.socialbackup.MessageType.BACKUP;
|
||||
import static org.briarproject.briar.socialbackup.MessageType.SHARD;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MessageEncoderImpl implements MessageEncoder {
|
||||
|
||||
private final ClientHelper clientHelper;
|
||||
|
||||
@Inject
|
||||
MessageEncoderImpl(ClientHelper clientHelper) {
|
||||
this.clientHelper = clientHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeShardMessage(Shard shard) {
|
||||
BdfList body = BdfList.of(
|
||||
SHARD.getValue(),
|
||||
shard.getSecretId(),
|
||||
shard.getNumShards(),
|
||||
shard.getThreshold(),
|
||||
shard.getShard()
|
||||
);
|
||||
return encodeBody(body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeBackupMessage(int version, BackupPayload payload) {
|
||||
BdfList body = BdfList.of(
|
||||
BACKUP.getValue(),
|
||||
version,
|
||||
payload.getBytes()
|
||||
);
|
||||
return encodeBody(body);
|
||||
}
|
||||
|
||||
private byte[] encodeBody(BdfList body) {
|
||||
try {
|
||||
return clientHelper.toByteArray(body);
|
||||
} catch (FormatException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.socialbackup.Shard;
|
||||
|
||||
@NotNullByDefault
|
||||
interface MessageParser {
|
||||
|
||||
Shard parseShardMessage(byte[] body) throws FormatException;
|
||||
|
||||
BackupPayload parseBackupMessage(byte[] body) throws FormatException;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.socialbackup.Shard;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MessageParserImpl implements MessageParser {
|
||||
|
||||
private final ClientHelper clientHelper;
|
||||
|
||||
@Inject
|
||||
MessageParserImpl(ClientHelper clientHelper) {
|
||||
this.clientHelper = clientHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shard parseShardMessage(byte[] body) throws FormatException {
|
||||
BdfList list = clientHelper.toList(body);
|
||||
// Message type, secret ID, num shards, threshold, shard
|
||||
byte[] secretId = list.getRaw(1);
|
||||
int numShards = list.getLong(2).intValue();
|
||||
int threshold = list.getLong(3).intValue();
|
||||
byte[] shard = list.getRaw(4);
|
||||
return new Shard(secretId, numShards, threshold, shard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BackupPayload parseBackupMessage(byte[] body)
|
||||
throws FormatException {
|
||||
BdfList list = clientHelper.toList(body);
|
||||
// Message type, backup payload
|
||||
return new BackupPayload(list.getRaw(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
enum MessageType {
|
||||
|
||||
SHARD(0), BACKUP(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
MessageType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
static MessageType fromValue(int value) throws FormatException {
|
||||
for (MessageType m : values()) if (m.value == value) return m;
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
interface SocialBackupConstants {
|
||||
|
||||
// Group metadata keys
|
||||
String GROUP_KEY_SECRET = "secret";
|
||||
String GROUP_KEY_CUSTODIANS = "custodians";
|
||||
String GROUP_KEY_THRESHOLD = "threshold";
|
||||
String GROUP_KEY_VERSION = "version";
|
||||
|
||||
// Message metadata keys
|
||||
String MSG_KEY_MESSAGE_TYPE = "messageType";
|
||||
String MSG_KEY_LOCAL = "local";
|
||||
String MSG_KEY_VERSION = "version";
|
||||
|
||||
/**
|
||||
* The length of the authenticated cipher's nonce in bytes.
|
||||
*/
|
||||
int NONCE_BYTES = 24;
|
||||
|
||||
/**
|
||||
* The length of the authenticated cipher's authentication tag in bytes.
|
||||
*/
|
||||
int AUTH_TAG_BYTES = 16;
|
||||
|
||||
/**
|
||||
* The length of the secret ID in bytes.
|
||||
*/
|
||||
int SECRET_ID_BYTES = 32;
|
||||
|
||||
/**
|
||||
* The maximum length of a shard in bytes.
|
||||
*/
|
||||
int MAX_SHARD_BYTES = 1024;
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.BdfIncomingMessageHook;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.Identity;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||
import org.briarproject.briar.api.socialbackup.BackupExistsException;
|
||||
import org.briarproject.briar.api.socialbackup.BackupMetadata;
|
||||
import org.briarproject.briar.api.socialbackup.Shard;
|
||||
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.briarproject.briar.socialbackup.MessageType.BACKUP;
|
||||
import static org.briarproject.briar.socialbackup.MessageType.SHARD;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_VERSION;
|
||||
|
||||
@NotNullByDefault
|
||||
class SocialBackupManagerImpl extends BdfIncomingMessageHook
|
||||
implements SocialBackupManager, OpenDatabaseHook, ContactHook,
|
||||
ClientVersioningHook {
|
||||
|
||||
private final ClientVersioningManager clientVersioningManager;
|
||||
private final TransportPropertyManager transportPropertyManager;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final BackupMetadataParser backupMetadataParser;
|
||||
private final BackupMetadataEncoder backupMetadataEncoder;
|
||||
private final BackupPayloadEncoder backupPayloadEncoder;
|
||||
private final MessageEncoder messageEncoder;
|
||||
private final IdentityManager identityManager;
|
||||
private final ContactManager contactManager;
|
||||
private final CryptoComponent crypto;
|
||||
private final DarkCrystal darkCrystal;
|
||||
private final Clock clock;
|
||||
private final Group localGroup;
|
||||
|
||||
@Inject
|
||||
SocialBackupManagerImpl(
|
||||
DatabaseComponent db,
|
||||
ClientHelper clientHelper,
|
||||
MetadataParser metadataParser,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
ContactGroupFactory contactGroupFactory,
|
||||
BackupMetadataParser backupMetadataParser,
|
||||
BackupMetadataEncoder backupMetadataEncoder,
|
||||
BackupPayloadEncoder backupPayloadEncoder,
|
||||
MessageEncoder messageEncoder,
|
||||
IdentityManager identityManager,
|
||||
ContactManager contactManager,
|
||||
CryptoComponent crypto,
|
||||
DarkCrystal darkCrystal,
|
||||
Clock clock) {
|
||||
super(db, clientHelper, metadataParser);
|
||||
this.clientVersioningManager = clientVersioningManager;
|
||||
this.transportPropertyManager = transportPropertyManager;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.backupMetadataParser = backupMetadataParser;
|
||||
this.backupMetadataEncoder = backupMetadataEncoder;
|
||||
this.backupPayloadEncoder = backupPayloadEncoder;
|
||||
this.messageEncoder = messageEncoder;
|
||||
this.identityManager = identityManager;
|
||||
this.contactManager = contactManager;
|
||||
this.crypto = crypto;
|
||||
this.darkCrystal = darkCrystal;
|
||||
localGroup =
|
||||
contactGroupFactory.createLocalGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatabaseOpened(Transaction txn) throws DbException {
|
||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||
db.addGroup(txn, localGroup);
|
||||
// Set things up for any pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(c);
|
||||
db.addGroup(txn, g);
|
||||
// Apply the client's visibility to the contact group
|
||||
Visibility client = clientVersioningManager.getClientVisibility(txn,
|
||||
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||
// TODO: Add the contact to our backup, if any
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
// TODO: Remove the contact from our backup, if any
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||
Visibility v) throws DbException {
|
||||
// Apply the client's visibility to the contact group
|
||||
Group g = getContactGroup(c);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary meta) throws DbException, FormatException {
|
||||
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
|
||||
if (type == SHARD) {
|
||||
// TODO: Add the shard to our backup, if any
|
||||
} else if (type == BACKUP) {
|
||||
// Keep the newest version of the backup, delete any older versions
|
||||
int version = meta.getLong(MSG_KEY_VERSION).intValue();
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, BACKUP.getValue()),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
Map<MessageId, BdfDictionary> results =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn,
|
||||
m.getGroupId(), query);
|
||||
if (results.size() > 1) throw new DbException();
|
||||
for (Entry<MessageId, BdfDictionary> e : results.entrySet()) {
|
||||
MessageId prevId = e.getKey();
|
||||
BdfDictionary prevMeta = e.getValue();
|
||||
int prevVersion = prevMeta.getLong(MSG_KEY_VERSION).intValue();
|
||||
if (version > prevVersion) {
|
||||
// This backup is newer - delete the previous backup
|
||||
db.deleteMessage(txn, prevId);
|
||||
db.deleteMessageMetadata(txn, prevId);
|
||||
} else {
|
||||
// We've already received a newer backup - delete this one
|
||||
db.deleteMessage(txn, m.getId());
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BackupMetadata getBackupMetadata(Transaction txn)
|
||||
throws DbException {
|
||||
try {
|
||||
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
|
||||
localGroup.getId());
|
||||
return backupMetadataParser.parseBackupMetadata(meta);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createBackup(Transaction txn, List<ContactId> custodianIds,
|
||||
int threshold) throws DbException {
|
||||
if (getBackupMetadata(txn) != null) throw new BackupExistsException();
|
||||
// Load the contacts
|
||||
List<Contact> custodians = new ArrayList<>(custodianIds.size());
|
||||
for (ContactId custodianId : custodianIds) {
|
||||
custodians.add(contactManager.getContact(txn, custodianId));
|
||||
}
|
||||
// Create the encrypted backup payload
|
||||
SecretKey secret = crypto.generateSecretKey();
|
||||
BackupPayload payload = createBackupPayload(txn, secret, 0);
|
||||
// Create the shards
|
||||
List<Shard> shards = darkCrystal.createShards(secret,
|
||||
custodians.size(), threshold);
|
||||
try {
|
||||
// Send the shard and backup messages to the custodians
|
||||
for (int i = 0; i < custodians.size(); i++) {
|
||||
Contact custodian = custodians.get(i);
|
||||
Shard shard = shards.get(i);
|
||||
sendShardMessage(txn, custodian, shard);
|
||||
sendBackupMessage(txn, custodian, 0, payload);
|
||||
}
|
||||
// Store the backup metadata
|
||||
List<Author> authors = new ArrayList<>(custodians.size());
|
||||
for (Contact custodian : custodians) {
|
||||
authors.add(custodian.getAuthor());
|
||||
}
|
||||
BackupMetadata backupMetadata =
|
||||
new BackupMetadata(secret, authors, threshold, 0);
|
||||
BdfDictionary meta =
|
||||
backupMetadataEncoder.encodeBackupMetadata(backupMetadata);
|
||||
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
|
||||
} catch (FormatException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, c);
|
||||
}
|
||||
|
||||
private BackupPayload createBackupPayload(Transaction txn,
|
||||
SecretKey secret, int version) throws DbException {
|
||||
Identity identity = identityManager.getIdentity(txn);
|
||||
Collection<Contact> contacts = contactManager.getContacts(txn);
|
||||
List<Contact> eligible = new ArrayList<>();
|
||||
List<Map<TransportId, TransportProperties>> properties =
|
||||
new ArrayList<>();
|
||||
// Include all contacts whose handshake public keys we know
|
||||
for (Contact c : contacts) {
|
||||
if (c.getHandshakePublicKey() != null) {
|
||||
eligible.add(c);
|
||||
properties.add(getTransportProperties(txn, c.getId()));
|
||||
// TODO: Include shard received from contact, if any
|
||||
}
|
||||
}
|
||||
return backupPayloadEncoder.encodeBackupPayload(secret, identity,
|
||||
eligible, properties, version);
|
||||
}
|
||||
|
||||
private Map<TransportId, TransportProperties> getTransportProperties(
|
||||
Transaction txn, ContactId c) throws DbException {
|
||||
// TODO: Include filtered properties for other transports
|
||||
TransportProperties p = transportPropertyManager
|
||||
.getRemoteProperties(txn, c, TorConstants.ID);
|
||||
return singletonMap(TorConstants.ID, p);
|
||||
}
|
||||
|
||||
private void sendShardMessage(Transaction txn, Contact custodian,
|
||||
Shard shard) throws DbException, FormatException {
|
||||
GroupId g = getContactGroup(custodian).getId();
|
||||
long timestamp = clock.currentTimeMillis();
|
||||
byte[] body = messageEncoder.encodeShardMessage(shard);
|
||||
Message m = clientHelper.createMessage(g, timestamp, body);
|
||||
BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, SHARD.getValue()),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||
}
|
||||
|
||||
private void sendBackupMessage(Transaction txn, Contact custodian,
|
||||
int version, BackupPayload payload)
|
||||
throws DbException, FormatException {
|
||||
GroupId g = getContactGroup(custodian).getId();
|
||||
long timestamp = clock.currentTimeMillis();
|
||||
byte[] body = messageEncoder.encodeBackupMessage(version, payload);
|
||||
Message m = clientHelper.createMessage(g, timestamp, body);
|
||||
BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, BACKUP.getValue()),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true),
|
||||
new BdfEntry(MSG_KEY_VERSION, version));
|
||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.briar.api.socialbackup.SocialBackupManager.CLIENT_ID;
|
||||
import static org.briarproject.briar.api.socialbackup.SocialBackupManager.MAJOR_VERSION;
|
||||
import static org.briarproject.briar.api.socialbackup.SocialBackupManager.MINOR_VERSION;
|
||||
|
||||
@Module
|
||||
public class SocialBackupModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
SocialBackupManager socialBackupManager;
|
||||
@Inject
|
||||
SocialBackupValidator socialBackupValidator;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SocialBackupManager socialBackupManager(
|
||||
LifecycleManager lifecycleManager,
|
||||
ContactManager contactManager,
|
||||
ValidationManager validationManager,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
SocialBackupManagerImpl socialBackupManager) {
|
||||
lifecycleManager.registerOpenDatabaseHook(socialBackupManager);
|
||||
contactManager.registerContactHook(socialBackupManager);
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
||||
MAJOR_VERSION, socialBackupManager);
|
||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||
MINOR_VERSION, socialBackupManager);
|
||||
return socialBackupManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SocialBackupValidator socialBackupValidator(
|
||||
ValidationManager validationManager,
|
||||
ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder,
|
||||
Clock clock) {
|
||||
SocialBackupValidator validator =
|
||||
new SocialBackupValidator(clientHelper, metadataEncoder, clock);
|
||||
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||
validator);
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Provides
|
||||
BackupMetadataParser backupMetadataParser(
|
||||
BackupMetadataParserImpl backupMetadataParser) {
|
||||
return backupMetadataParser;
|
||||
}
|
||||
|
||||
@Provides
|
||||
BackupMetadataEncoder backupMetadataEncoder(
|
||||
BackupMetadataEncoderImpl backupMetadataEncoder) {
|
||||
return backupMetadataEncoder;
|
||||
}
|
||||
|
||||
@Provides
|
||||
BackupPayloadEncoder backupPayloadEncoder(
|
||||
BackupPayloadEncoderImpl backupPayloadEncoder) {
|
||||
return backupPayloadEncoder;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MessageEncoder messageEncoder(MessageEncoderImpl messageEncoder) {
|
||||
return messageEncoder;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MessageParser messageParser(MessageParserImpl messageParser) {
|
||||
return messageParser;
|
||||
}
|
||||
|
||||
@Provides
|
||||
DarkCrystal darkCrystal(DarkCrystalStub darkCrystal) {
|
||||
// TODO: Replace this with a real implementation
|
||||
return darkCrystal;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
import org.briarproject.bramble.api.client.BdfMessageValidator;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
import static org.briarproject.briar.socialbackup.MessageType.BACKUP;
|
||||
import static org.briarproject.briar.socialbackup.MessageType.SHARD;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MAX_SHARD_BYTES;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.MSG_KEY_VERSION;
|
||||
import static org.briarproject.briar.socialbackup.SocialBackupConstants.SECRET_ID_BYTES;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class SocialBackupValidator extends BdfMessageValidator {
|
||||
|
||||
@Inject
|
||||
SocialBackupValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
|
||||
if (type == SHARD) return validateShardMessage(body);
|
||||
else if (type == BACKUP) return validateBackupMessage(body);
|
||||
else throw new AssertionError();
|
||||
}
|
||||
|
||||
private BdfMessageContext validateShardMessage(BdfList body)
|
||||
throws FormatException {
|
||||
// Message type, secret ID, num shards, threshold, shard
|
||||
checkSize(body, 5);
|
||||
byte[] secretId = body.getRaw(1);
|
||||
checkLength(secretId, SECRET_ID_BYTES);
|
||||
int numShards = body.getLong(2).intValue();
|
||||
if (numShards < 2) throw new FormatException();
|
||||
int threshold = body.getLong(3).intValue();
|
||||
if (threshold < 2) throw new FormatException();
|
||||
if (threshold > numShards) throw new FormatException();
|
||||
byte[] shard = body.getRaw(4);
|
||||
checkLength(shard, 1, MAX_SHARD_BYTES);
|
||||
BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, SHARD.getValue()),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false));
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
|
||||
private BdfMessageContext validateBackupMessage(BdfList body)
|
||||
throws FormatException {
|
||||
// Message type, version, backup payload
|
||||
checkSize(body, 3);
|
||||
int version = body.getLong(1).intValue();
|
||||
if (version < 0) throw new FormatException();
|
||||
byte[] payload = body.getRaw(2);
|
||||
BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, BACKUP.getValue()),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false),
|
||||
new BdfEntry(MSG_KEY_VERSION, version));
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.briarproject.briar.socialbackup;
|
||||
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||
import org.briarproject.briar.api.socialbackup.BackupMetadata;
|
||||
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
||||
import org.briarproject.briar.test.BriarIntegrationTest;
|
||||
import org.briarproject.briar.test.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class SocialBackupIntegrationTest
|
||||
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
|
||||
|
||||
private SocialBackupManager socialBackupManager0;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
socialBackupManager0 = c0.getSocialBackupManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createComponents() {
|
||||
BriarIntegrationTestComponent component =
|
||||
DaggerBriarIntegrationTestComponent.builder().build();
|
||||
BriarIntegrationTestComponent.Helper.injectEagerSingletons(component);
|
||||
component.inject(this);
|
||||
|
||||
c0 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(new TestDatabaseConfigModule(t0Dir))
|
||||
.build();
|
||||
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c0);
|
||||
|
||||
c1 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(new TestDatabaseConfigModule(t1Dir))
|
||||
.build();
|
||||
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c1);
|
||||
|
||||
c2 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(new TestDatabaseConfigModule(t2Dir))
|
||||
.build();
|
||||
BriarIntegrationTestComponent.Helper.injectEagerSingletons(c2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateBackup() throws Exception {
|
||||
// Create the backup
|
||||
db0.transaction(false, txn -> {
|
||||
assertNull(socialBackupManager0.getBackupMetadata(txn));
|
||||
socialBackupManager0.createBackup(txn,
|
||||
asList(contactId1From0, contactId2From0), 2);
|
||||
BackupMetadata backupMetadata =
|
||||
socialBackupManager0.getBackupMetadata(txn);
|
||||
assertNotNull(backupMetadata);
|
||||
List<Author> expected = asList(contact1From0.getAuthor(),
|
||||
contact2From0.getAuthor());
|
||||
assertEquals(expected, backupMetadata.getCustodians());
|
||||
assertEquals(2, backupMetadata.getThreshold());
|
||||
assertEquals(0, backupMetadata.getVersion());
|
||||
});
|
||||
// Sync the shard and backup messages to the contacts
|
||||
sync0To1(2, true);
|
||||
sync0To2(2, true);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
|
||||
import org.briarproject.briar.avatar.AvatarModule;
|
||||
import org.briarproject.briar.blog.BlogModule;
|
||||
import org.briarproject.briar.forum.ForumModule;
|
||||
@@ -36,6 +37,7 @@ import org.briarproject.briar.messaging.MessagingModule;
|
||||
import org.briarproject.briar.privategroup.PrivateGroupModule;
|
||||
import org.briarproject.briar.privategroup.invitation.GroupInvitationModule;
|
||||
import org.briarproject.briar.sharing.SharingModule;
|
||||
import org.briarproject.briar.socialbackup.SocialBackupModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -70,6 +72,8 @@ public interface BriarIntegrationTestComponent
|
||||
|
||||
void inject(SharingModule.EagerSingletons init);
|
||||
|
||||
void inject(SocialBackupModule.EagerSingletons init);
|
||||
|
||||
LifecycleManager getLifecycleManager();
|
||||
|
||||
EventBus getEventBus();
|
||||
@@ -116,6 +120,8 @@ public interface BriarIntegrationTestComponent
|
||||
|
||||
ConnectionManager getConnectionManager();
|
||||
|
||||
SocialBackupManager getSocialBackupManager();
|
||||
|
||||
class Helper {
|
||||
|
||||
public static void injectEagerSingletons(
|
||||
@@ -131,6 +137,7 @@ public interface BriarIntegrationTestComponent
|
||||
c.inject(new MessagingModule.EagerSingletons());
|
||||
c.inject(new PrivateGroupModule.EagerSingletons());
|
||||
c.inject(new SharingModule.EagerSingletons());
|
||||
c.inject(new SocialBackupModule.EagerSingletons());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user