diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/AuthenticatedCipher.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/AuthenticatedCipher.java similarity index 92% rename from bramble-core/src/main/java/org/briarproject/bramble/crypto/AuthenticatedCipher.java rename to bramble-api/src/main/java/org/briarproject/bramble/api/crypto/AuthenticatedCipher.java index b70358e5d..f48731209 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/AuthenticatedCipher.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/AuthenticatedCipher.java @@ -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 diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java index be2e28d3e..f9922e2cd 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/IdentityManager.java @@ -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. */ diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java index de10f2aca..11c32ba37 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/properties/TransportPropertyManager.java @@ -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. diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java index 7a30b7ce6..0ac2d3d3e 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java @@ -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; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java index 680391bd0..435878e3d 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoModule.java @@ -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; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java index 093136662..b1c9d209c 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java @@ -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; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterImpl.java index 16edc2c74..38c0c4fe1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterImpl.java @@ -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; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java index ae8db9fb7..42d6c790b 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java @@ -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; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java index 2a060b03f..604abf1eb 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java @@ -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; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/XSalsa20Poly1305AuthenticatedCipher.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/XSalsa20Poly1305AuthenticatedCipher.java index fe1f38d9e..9c6908dd0 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/XSalsa20Poly1305AuthenticatedCipher.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/XSalsa20Poly1305AuthenticatedCipher.java @@ -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; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java index d5d5e0b6f..d61fbc2dd 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityManagerImpl.java @@ -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(); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java index d5f2d4212..7ce65d0f9 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java @@ -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 diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamDecrypterImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamDecrypterImplTest.java index e5ea2d31d..33011269a 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamDecrypterImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamDecrypterImplTest.java @@ -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; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamEncrypterImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamEncrypterImplTest.java index c720805bd..39a20141e 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamEncrypterImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamEncrypterImplTest.java @@ -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; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/TestAuthenticatedCipher.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/TestAuthenticatedCipher.java index dd7982795..fe5e504c1 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/TestAuthenticatedCipher.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/TestAuthenticatedCipher.java @@ -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; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/XSalsa20Poly1305AuthenticatedCipherTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/XSalsa20Poly1305AuthenticatedCipherTest.java index 9ddcc9487..11e4c6b0e 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/XSalsa20Poly1305AuthenticatedCipherTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/XSalsa20Poly1305AuthenticatedCipherTest.java @@ -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; diff --git a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/BackupExistsException.java b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/BackupExistsException.java new file mode 100644 index 000000000..c28e8f8b8 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/BackupExistsException.java @@ -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 { +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/BackupMetadata.java b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/BackupMetadata.java new file mode 100644 index 000000000..53ea2c7ab --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/BackupMetadata.java @@ -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 custodians; + private final int threshold, version; + + public BackupMetadata(SecretKey secret, List custodians, + int threshold, int version) { + this.secret = secret; + this.custodians = custodians; + this.threshold = threshold; + this.version = version; + } + + public SecretKey getSecret() { + return secret; + } + + public List getCustodians() { + return custodians; + } + + public int getThreshold() { + return threshold; + } + + public int getVersion() { + return version; + } +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/Shard.java b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/Shard.java new file mode 100644 index 000000000..07a16a77c --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/Shard.java @@ -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; + } +} diff --git a/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackupManager.java b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackupManager.java new file mode 100644 index 000000000..acab2eac1 --- /dev/null +++ b/briar-api/src/main/java/org/briarproject/briar/api/socialbackup/SocialBackupManager.java @@ -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 custodianIds, + int threshold) throws DbException; +} diff --git a/briar-core/src/main/java/org/briarproject/briar/BriarCoreEagerSingletons.java b/briar-core/src/main/java/org/briarproject/briar/BriarCoreEagerSingletons.java index 72c5cf603..163e68534 100644 --- a/briar-core/src/main/java/org/briarproject/briar/BriarCoreEagerSingletons.java +++ b/briar-core/src/main/java/org/briarproject/briar/BriarCoreEagerSingletons.java @@ -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()); } } } diff --git a/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java b/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java index 222e41a96..2144fa654 100644 --- a/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/BriarCoreModule.java @@ -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 { diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataEncoder.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataEncoder.java new file mode 100644 index 000000000..7a0369750 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataEncoder.java @@ -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); +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataEncoderImpl.java new file mode 100644 index 000000000..b646ffafc --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataEncoderImpl.java @@ -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; + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataParser.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataParser.java new file mode 100644 index 000000000..5f15eba44 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataParser.java @@ -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; +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataParserImpl.java new file mode 100644 index 000000000..a32bb7866 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupMetadataParserImpl.java @@ -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 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); + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayload.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayload.java new file mode 100644 index 000000000..e281cdd9b --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayload.java @@ -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); + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java new file mode 100644 index 000000000..2c3abc411 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoder.java @@ -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 contacts, + List> properties, + int version); +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java new file mode 100644 index 000000000..3e796b3dd --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/BackupPayloadEncoderImpl.java @@ -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 cipherProvider; + private final SecureRandom secureRandom; + + @Inject + BackupPayloadEncoderImpl(ClientHelper clientHelper, + Provider cipherProvider, + SecureRandom secureRandom) { + this.clientHelper = clientHelper; + this.cipherProvider = cipherProvider; + this.secureRandom = secureRandom; + } + + @Override + public BackupPayload encodeBackupPayload(SecretKey secret, + Identity identity, List contacts, + List> 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 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); + } + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/DarkCrystal.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/DarkCrystal.java new file mode 100644 index 000000000..9bac55e28 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/DarkCrystal.java @@ -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 createShards(SecretKey secret, int shards, int threshold); +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/DarkCrystalStub.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/DarkCrystalStub.java new file mode 100644 index 000000000..1f260c9da --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/DarkCrystalStub.java @@ -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 createShards(SecretKey secret, int numShards, + int threshold) { + Random random = new Random(); + byte[] secretId = new byte[SECRET_ID_BYTES]; + random.nextBytes(secretId); + List 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; + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageEncoder.java new file mode 100644 index 000000000..e2c8ce23a --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageEncoder.java @@ -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); +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageEncoderImpl.java new file mode 100644 index 000000000..9d5118ad7 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageEncoderImpl.java @@ -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); + } + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageParser.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageParser.java new file mode 100644 index 000000000..216e1bbf8 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageParser.java @@ -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; +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageParserImpl.java new file mode 100644 index 000000000..3a769e732 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageParserImpl.java @@ -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)); + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageType.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageType.java new file mode 100644 index 000000000..e146fcf17 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/MessageType.java @@ -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(); + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupConstants.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupConstants.java new file mode 100644 index 000000000..2f40b8090 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupConstants.java @@ -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; +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java new file mode 100644 index 000000000..e1ec098a3 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupManagerImpl.java @@ -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 results = + clientHelper.getMessageMetadataAsDictionary(txn, + m.getGroupId(), query); + if (results.size() > 1) throw new DbException(); + for (Entry 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 custodianIds, + int threshold) throws DbException { + if (getBackupMetadata(txn) != null) throw new BackupExistsException(); + // Load the contacts + List 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 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 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 contacts = contactManager.getContacts(txn); + List eligible = new ArrayList<>(); + List> 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 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); + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupModule.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupModule.java new file mode 100644 index 000000000..4e568b4af --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupModule.java @@ -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; + } +} diff --git a/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupValidator.java b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupValidator.java new file mode 100644 index 000000000..ea2d232b2 --- /dev/null +++ b/briar-core/src/main/java/org/briarproject/briar/socialbackup/SocialBackupValidator.java @@ -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); + } +} diff --git a/briar-core/src/test/java/org/briarproject/briar/socialbackup/SocialBackupIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/socialbackup/SocialBackupIntegrationTest.java new file mode 100644 index 000000000..0ba50fcd2 --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/socialbackup/SocialBackupIntegrationTest.java @@ -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 { + + 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 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); + } +} diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java index fa65d46f5..6092cd47f 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java +++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java @@ -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()); } } }