social backup exchange

This commit is contained in:
ameba23
2021-03-30 12:06:45 +02:00
parent a11a81f3d4
commit d129186bab
9 changed files with 154 additions and 133 deletions

View File

@@ -9,6 +9,6 @@ import java.util.List;
@NotNullByDefault @NotNullByDefault
interface BackupPayloadEncoder { interface BackupPayloadEncoder {
BackupPayload encodeBackupPayload(SecretKey secret, Identity identity, org.briarproject.briar.api.socialbackup.BackupPayload encodeBackupPayload(SecretKey secret, Identity identity,
List<ContactData> contactData, int version); List<ContactData> contactData, int version);
} }

View File

@@ -44,7 +44,7 @@ class BackupPayloadEncoderImpl implements BackupPayloadEncoder {
} }
@Override @Override
public BackupPayload encodeBackupPayload(SecretKey secret, public org.briarproject.briar.api.socialbackup.BackupPayload encodeBackupPayload(SecretKey secret,
Identity identity, List<ContactData> contactData, int version) { Identity identity, List<ContactData> contactData, int version) {
// Encode the local identity // Encode the local identity
BdfList bdfIdentity = new BdfList(); BdfList bdfIdentity = new BdfList();
@@ -83,7 +83,7 @@ class BackupPayloadEncoderImpl implements BackupPayloadEncoder {
int encrypted = cipher.process(plaintext, 0, plaintext.length, int encrypted = cipher.process(plaintext, 0, plaintext.length,
ciphertext, 0); ciphertext, 0);
if (encrypted != ciphertext.length) throw new AssertionError(); if (encrypted != ciphertext.length) throw new AssertionError();
return new BackupPayload(ciphertext); return new org.briarproject.briar.api.socialbackup.BackupPayload(ciphertext);
} catch (FormatException | GeneralSecurityException e) { } catch (FormatException | GeneralSecurityException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }

View File

@@ -8,5 +8,5 @@ interface MessageEncoder {
byte[] encodeShardMessage(Shard shard); byte[] encodeShardMessage(Shard shard);
byte[] encodeBackupMessage(int version, BackupPayload payload); byte[] encodeBackupMessage(int version, org.briarproject.briar.api.socialbackup.BackupPayload payload);
} }

View File

@@ -34,7 +34,7 @@ class MessageEncoderImpl implements MessageEncoder {
} }
@Override @Override
public byte[] encodeBackupMessage(int version, BackupPayload payload) { public byte[] encodeBackupMessage(int version, org.briarproject.briar.api.socialbackup.BackupPayload payload) {
BdfList body = BdfList.of( BdfList body = BdfList.of(
BACKUP.getValue(), BACKUP.getValue(),
version, version,

View File

@@ -10,5 +10,5 @@ interface MessageParser {
Shard parseShardMessage(BdfList body) throws FormatException; Shard parseShardMessage(BdfList body) throws FormatException;
BackupPayload parseBackupMessage(BdfList body) throws FormatException; org.briarproject.briar.api.socialbackup.BackupPayload parseBackupMessage(BdfList body) throws FormatException;
} }

View File

@@ -25,9 +25,9 @@ class MessageParserImpl implements MessageParser {
} }
@Override @Override
public BackupPayload parseBackupMessage(BdfList body) public org.briarproject.briar.api.socialbackup.BackupPayload parseBackupMessage(BdfList body)
throws FormatException { throws FormatException {
// Message type, backup payload // Message type, backup payload
return new BackupPayload(body.getRaw(1)); return new org.briarproject.briar.api.socialbackup.BackupPayload(body.getRaw(1));
} }
} }

View File

@@ -3,17 +3,16 @@ package org.briarproject.briar.socialbackup;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate; import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactExchangeConstants;
import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactExchangeRecordTypes;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -30,7 +29,11 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter; import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory; import org.briarproject.bramble.api.transport.StreamWriterFactory;
import org.briarproject.bramble.contact.ContactExchangeCrypto; import org.briarproject.bramble.api.contact.ContactExchangeCrypto;
import org.briarproject.briar.api.socialbackup.ReturnShardPayload;
import org.briarproject.briar.api.socialbackup.Shard;
import org.briarproject.briar.api.socialbackup.BackupPayload;
import org.briarproject.briar.api.socialbackup.SocialBackupExchangeManager;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
@@ -43,28 +46,30 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import sun.java2d.xr.XRBackendNative;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.contact.ContactExchangeRecordTypes.CONTACT_INFO;
import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class SocialBackupExchangeManagerImpl implements ContactExchangeManager { class SocialBackupExchangeManagerImpl implements SocialBackupExchangeManager {
private static final Logger LOG = private static final Logger LOG =
getLogger(SocialBackupExchangeManagerImpl.class.getName()); getLogger(SocialBackupExchangeManagerImpl.class.getName());
// Accept records with current protocol version, known record type // Accept records with current protocol version, known record type
private static final Predicate<Record> ACCEPT = r -> private static final Predicate<Record> ACCEPT = r ->
r.getProtocolVersion() == ContactExchangeConstants.PROTOCOL_VERSION && r.getProtocolVersion() ==
ContactExchangeConstants.PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r -> private static final Predicate<Record> IGNORE = r ->
r.getProtocolVersion() == ContactExchangeConstants.PROTOCOL_VERSION && r.getProtocolVersion() ==
ContactExchangeConstants.PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());
private static boolean isKnownRecordType(byte type) { private static boolean isKnownRecordType(byte type) {
@@ -77,17 +82,19 @@ class SocialBackupExchangeManagerImpl implements ContactExchangeManager {
private final RecordWriterFactory recordWriterFactory; private final RecordWriterFactory recordWriterFactory;
private final Clock clock; private final Clock clock;
private final ContactManager contactManager; private final ContactManager contactManager;
private final IdentityManager identityManager; // private final IdentityManager identityManager;
private final TransportPropertyManager transportPropertyManager; private final TransportPropertyManager transportPropertyManager;
private final ContactExchangeCrypto contactExchangeCrypto; private final ContactExchangeCrypto contactExchangeCrypto;
private final StreamReaderFactory streamReaderFactory; private final StreamReaderFactory streamReaderFactory;
private final StreamWriterFactory streamWriterFactory; private final StreamWriterFactory streamWriterFactory;
@Inject @Inject
SocialBackupExchangeManagerImpl(DatabaseComponent db, ClientHelper clientHelper, SocialBackupExchangeManagerImpl(DatabaseComponent db,
ClientHelper clientHelper,
RecordReaderFactory recordReaderFactory, RecordReaderFactory recordReaderFactory,
RecordWriterFactory recordWriterFactory, Clock clock, RecordWriterFactory recordWriterFactory, Clock clock,
ContactManager contactManager, IdentityManager identityManager, ContactManager contactManager,
// IdentityManager identityManager,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager,
ContactExchangeCrypto contactExchangeCrypto, ContactExchangeCrypto contactExchangeCrypto,
StreamReaderFactory streamReaderFactory, StreamReaderFactory streamReaderFactory,
@@ -98,7 +105,7 @@ class SocialBackupExchangeManagerImpl implements ContactExchangeManager {
this.recordWriterFactory = recordWriterFactory; this.recordWriterFactory = recordWriterFactory;
this.clock = clock; this.clock = clock;
this.contactManager = contactManager; this.contactManager = contactManager;
this.identityManager = identityManager; // this.identityManager = identityManager;
this.transportPropertyManager = transportPropertyManager; this.transportPropertyManager = transportPropertyManager;
this.contactExchangeCrypto = contactExchangeCrypto; this.contactExchangeCrypto = contactExchangeCrypto;
this.streamReaderFactory = streamReaderFactory; this.streamReaderFactory = streamReaderFactory;
@@ -106,70 +113,16 @@ class SocialBackupExchangeManagerImpl implements ContactExchangeManager {
} }
@Override @Override
public void sendSocialBackup(DuplexTransportConnection conn, public void sendReturnShard(DuplexTransportConnection conn,
SecretKey masterKey, boolean alice, SecretKey masterKey,
boolean verified) throws IOException, DbException {
return exchange(null, conn, masterKey, alice, verified);
}
@Override
public ReturnShardPayload receiveSocialBackup(DuplexTransportConnection conn,
SecretKey masterKey, boolean verified) throws IOException, DbException {
boolean alice = false;
InputStream in = conn.getReader().getInputStream();
Map<TransportId, TransportProperties> localProperties =
transportPropertyManager.getLocalProperties();
// Derive the header keys for the transport streams
SecretKey remoteHeaderKey =
contactExchangeCrypto.deriveHeaderKey(masterKey, !alice);
// Create the readers
InputStream streamReader = streamReaderFactory
.createContactExchangeStreamReader(in, remoteHeaderKey);
RecordReader recordReader =
recordReaderFactory.createRecordReader(streamReader);
long localTimestamp = clock.currentTimeMillis();
ContactInfo remoteInfo;
remoteInfo = receiveContactInfo(recordReader);
// Skip any remaining records from the incoming stream
recordReader.readRecord(r -> false, IGNORE);
// Verify the contact's signature
PublicKey remotePublicKey = remoteInfo.author.getPublicKey();
if (!contactExchangeCrypto.verify(remotePublicKey,
masterKey, !alice, remoteInfo.signature)) {
LOG.warning("Invalid signature");
throw new FormatException();
}
// The agreed timestamp is the minimum of the peers' timestamps
long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
// Contact exchange succeeded
LOG.info("Received social backup");
return contact;
}
// @Override
// public Contact exchangeContacts(PendingContactId p,
// DuplexTransportConnection conn, SecretKey masterKey, boolean alice,
// boolean verified) throws IOException, DbException {
// return exchange(p, conn, masterKey, alice, verified);
// }
private Contact exchange(@Nullable PendingContactId p,
DuplexTransportConnection conn, SecretKey masterKey, boolean alice,
boolean verified) throws IOException, DbException { boolean verified) throws IOException, DbException {
boolean alice = true;
// Get the transport connection's input and output streams // Get the transport connection's input and output streams
InputStream in = conn.getReader().getInputStream(); InputStream in = conn.getReader().getInputStream();
OutputStream out = conn.getWriter().getOutputStream(); OutputStream out = conn.getWriter().getOutputStream();
// Get the local author and transport properties // Get the local author and transport properties
LocalAuthor localAuthor = identityManager.getLocalAuthor(); // LocalAuthor localAuthor = identityManager.getLocalAuthor();
Map<TransportId, TransportProperties> localProperties = Map<TransportId, TransportProperties> localProperties =
transportPropertyManager.getLocalProperties(); transportPropertyManager.getLocalProperties();
@@ -192,21 +145,14 @@ class SocialBackupExchangeManagerImpl implements ContactExchangeManager {
.createRecordWriter(streamWriter.getOutputStream()); .createRecordWriter(streamWriter.getOutputStream());
// Create our signature // Create our signature
byte[] localSignature = contactExchangeCrypto // byte[] localSignature = contactExchangeCrypto
.sign(localAuthor.getPrivateKey(), masterKey, alice); // .sign(localAuthor.getPrivateKey(), masterKey, alice);
// Exchange contact info // Exchange contact info
long localTimestamp = clock.currentTimeMillis(); long localTimestamp = clock.currentTimeMillis();
ContactInfo remoteInfo;
if (alice) { sendShardPayload(recordWriter, localProperties, returnShardPayload, localTimestamp);
sendShardAndBackup(recordWriter, localAuthor, localProperties, receiveAcknowledgement(recordReader);
localSignature, localTimestamp);
remoteAcknowledgement = receiveRemoteAcknowledgement(recordReader);
} else {
remoteInfo = receiveContactInfo(recordReader);
sendContactInfo(recordWriter, localAuthor, localProperties,
localSignature, localTimestamp);
}
// Send EOF on the outgoing stream // Send EOF on the outgoing stream
streamWriter.sendEndOfStream(); streamWriter.sendEndOfStream();
@@ -215,71 +161,140 @@ class SocialBackupExchangeManagerImpl implements ContactExchangeManager {
recordReader.readRecord(r -> false, IGNORE); recordReader.readRecord(r -> false, IGNORE);
// Verify the contact's signature // Verify the contact's signature
PublicKey remotePublicKey = remoteInfo.author.getPublicKey(); // PublicKey remotePublicKey = remoteInfo.author.getPublicKey();
if (!contactExchangeCrypto.verify(remotePublicKey, // if (!contactExchangeCrypto.verify(remotePublicKey,
masterKey, !alice, remoteInfo.signature)) { // masterKey, !alice, remoteInfo.signature)) {
LOG.warning("Invalid signature"); // LOG.warning("Invalid signature");
throw new FormatException(); // throw new FormatException();
} // }
// The agreed timestamp is the minimum of the peers' timestamps // The agreed timestamp is the minimum of the peers' timestamps
long timestamp = Math.min(localTimestamp, remoteInfo.timestamp); // long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
// Add the contact LOG.info("Social backup sent");
Contact contact = addContact(p, remoteInfo.author, localAuthor, }
masterKey, timestamp, alice, verified, remoteInfo.properties);
@Override
public ReturnShardPayload receiveReturnShard(DuplexTransportConnection conn,
SecretKey masterKey, boolean verified)
throws IOException, DbException {
boolean alice = false;
// Get the transport connection's input and output streams
InputStream in = conn.getReader().getInputStream();
OutputStream out = conn.getWriter().getOutputStream();
// Get the local author and transport properties
// LocalAuthor localAuthor = identityManager.getLocalAuthor();
Map<TransportId, TransportProperties> localProperties =
transportPropertyManager.getLocalProperties();
// Derive the header keys for the transport streams
SecretKey localHeaderKey =
contactExchangeCrypto.deriveHeaderKey(masterKey, alice);
SecretKey remoteHeaderKey =
contactExchangeCrypto.deriveHeaderKey(masterKey, !alice);
// Create the readers
InputStream streamReader = streamReaderFactory
.createContactExchangeStreamReader(in, remoteHeaderKey);
RecordReader recordReader =
recordReaderFactory.createRecordReader(streamReader);
// Create the writers
StreamWriter streamWriter = streamWriterFactory
.createContactExchangeStreamWriter(out, localHeaderKey);
RecordWriter recordWriter = recordWriterFactory
.createRecordWriter(streamWriter.getOutputStream());
// Create our signature
// byte[] localSignature = contactExchangeCrypto
// .sign(localAuthor.getPrivateKey(), masterKey, alice);
// Exchange contact info
long localTimestamp = clock.currentTimeMillis();
ReturnShardPayload returnShardPayload =
receiveShardPayload(recordReader);
sendAcknowledgement(recordWriter, localProperties, localTimestamp);
// Send EOF on the outgoing stream
streamWriter.sendEndOfStream();
// Skip any remaining records from the incoming stream
recordReader.readRecord(r -> false, IGNORE);
// Verify the contact's signature
// PublicKey remotePublicKey = remoteInfo.author.getPublicKey();
// if (!contactExchangeCrypto.verify(remotePublicKey,
// masterKey, !alice, remoteInfo.signature)) {
// LOG.warning("Invalid signature");
// throw new FormatException();
// }
// The agreed timestamp is the minimum of the peers' timestamps
// long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
// Contact exchange succeeded // Contact exchange succeeded
LOG.info("Contact exchange succeeded"); LOG.info("Received shard payload");
return contact; return returnShardPayload;
} }
private void sendContactInfo(RecordWriter recordWriter, Author author, private void sendShardPayload(RecordWriter recordWriter,
Map<TransportId, TransportProperties> properties, byte[] signature, Map<TransportId, TransportProperties> properties,
ReturnShardPayload returnShardPayload,
long timestamp) throws IOException { long timestamp) throws IOException {
BdfList authorList = clientHelper.toList(author); // BdfList authorList = clientHelper.toList(author);
BdfDictionary props = clientHelper.toDictionary(properties); BdfDictionary props = clientHelper.toDictionary(properties);
BdfList payload = BdfList.of(authorList, props, signature, timestamp); Shard shard = returnShardPayload.getShard();
BdfList shardList = BdfList.of(shard.getSecretId(), shard.getShard());
BdfList payload = BdfList.of(shardList,
returnShardPayload.getBackupPayload().getBytes(), timestamp);
recordWriter.writeRecord(new Record( recordWriter.writeRecord(new Record(
ContactExchangeConstants.PROTOCOL_VERSION, ContactExchangeRecordTypes.CONTACT_INFO, ContactExchangeConstants.PROTOCOL_VERSION,
SocialBackupExchangeRecordTypes.RETURN_SHARD,
clientHelper.toByteArray(payload))); clientHelper.toByteArray(payload)));
recordWriter.flush(); recordWriter.flush();
LOG.info("Sent contact info"); LOG.info("Sent shard and encrypted backup");
} }
private ContactInfo receiveContactInfo(RecordReader recordReader) private ReturnShardPayload receiveShardPayload(RecordReader recordReader)
throws IOException { throws IOException {
Record record = recordReader.readRecord(ACCEPT, IGNORE); Record record = recordReader.readRecord(ACCEPT, IGNORE);
if (record == null) throw new EOFException(); if (record == null) throw new EOFException();
LOG.info("Received contact info"); LOG.info("Received shard and encrypted backup");
BdfList payload = clientHelper.toList(record.getPayload()); BdfList payload = clientHelper.toList(record.getPayload());
checkSize(payload, 4); checkSize(payload, 3);
Author author = clientHelper.parseAndValidateAuthor(payload.getList(0)); BdfList shardList = payload.getList(0);
BdfDictionary props = payload.getDictionary(1); Shard shard = new Shard(shardList.getRaw(0), shardList.getRaw(1));
Map<TransportId, TransportProperties> properties = BackupPayload backupPayload = new BackupPayload(payload.getRaw(1));
clientHelper.parseAndValidateTransportPropertiesMap(props); // Map<TransportId, TransportProperties> properties =
byte[] signature = payload.getRaw(2); // clientHelper.parseAndValidateTransportPropertiesMap(props);
checkLength(signature, 1, MAX_SIGNATURE_LENGTH); // byte[] signature = payload.getRaw(2);
long timestamp = payload.getLong(3); // checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
long timestamp = payload.getLong(2);
if (timestamp < 0) throw new FormatException(); if (timestamp < 0) throw new FormatException();
return new ContactInfo(author, properties, signature, timestamp); return new ReturnShardPayload(shard, backupPayload);
} }
private void sendAcknowledgement(RecordWriter recordWriter,
Map<TransportId, TransportProperties> properties,
long timestamp) throws IOException {
private static class ContactInfo { BdfList payload = BdfList.of(timestamp);
private final Author author; recordWriter.writeRecord(new Record(
private final Map<TransportId, TransportProperties> properties; ContactExchangeConstants.PROTOCOL_VERSION,
private final byte[] signature; SocialBackupExchangeRecordTypes.ACKNOWLEDGEMENT,
private final long timestamp; clientHelper.toByteArray(payload)));
recordWriter.flush();
}
private ContactInfo(Author author, private void receiveAcknowledgement(RecordReader recordReader)
Map<TransportId, TransportProperties> properties, throws IOException {
byte[] signature, long timestamp) { Record record = recordReader.readRecord(ACCEPT, IGNORE);
this.author = author; if (record == null) throw new EOFException();
this.properties = properties; BdfList payload = clientHelper.toList(record.getPayload());
this.signature = signature; checkSize(payload, 1);
this.timestamp = timestamp; long timestamp = payload.getLong(0);
} if (timestamp < 0) throw new FormatException();
} }
} }

View File

@@ -1,4 +1,9 @@
package org.briarproject.briar.socialbackup; package org.briarproject.briar.socialbackup;
/**
* Record types for the return shard protocol.
*/
public interface SocialBackupExchangeRecordTypes { public interface SocialBackupExchangeRecordTypes {
byte RETURN_SHARD = 0;
byte ACKNOWLEDGEMENT = 1;
} }

View File

@@ -46,6 +46,7 @@ import org.briarproject.briar.api.socialbackup.BackupExistsException;
import org.briarproject.briar.api.socialbackup.BackupMetadata; import org.briarproject.briar.api.socialbackup.BackupMetadata;
import org.briarproject.briar.api.socialbackup.DarkCrystal; import org.briarproject.briar.api.socialbackup.DarkCrystal;
import org.briarproject.briar.api.socialbackup.Shard; import org.briarproject.briar.api.socialbackup.Shard;
import org.briarproject.briar.api.socialbackup.BackupPayload;
import org.briarproject.briar.api.socialbackup.ShardMessageHeader; import org.briarproject.briar.api.socialbackup.ShardMessageHeader;
import org.briarproject.briar.api.socialbackup.ShardReceivedEvent; import org.briarproject.briar.api.socialbackup.ShardReceivedEvent;
import org.briarproject.briar.api.socialbackup.SocialBackupManager; import org.briarproject.briar.api.socialbackup.SocialBackupManager;