mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 19:29:06 +01:00
Add transport key agreement client.
This commit is contained in:
@@ -11,6 +11,7 @@ import org.briarproject.bramble.properties.PropertiesModule;
|
||||
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
||||
import org.briarproject.bramble.sync.validation.ValidationModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
|
||||
import org.briarproject.bramble.versioning.VersioningModule;
|
||||
|
||||
public interface BrambleCoreEagerSingletons {
|
||||
@@ -33,6 +34,8 @@ public interface BrambleCoreEagerSingletons {
|
||||
|
||||
void inject(RendezvousModule.EagerSingletons init);
|
||||
|
||||
void inject(TransportKeyAgreementModule.EagerSingletons init);
|
||||
|
||||
void inject(TransportModule.EagerSingletons init);
|
||||
|
||||
void inject(ValidationModule.EagerSingletons init);
|
||||
@@ -51,6 +54,7 @@ public interface BrambleCoreEagerSingletons {
|
||||
c.inject(new RendezvousModule.EagerSingletons());
|
||||
c.inject(new PluginModule.EagerSingletons());
|
||||
c.inject(new PropertiesModule.EagerSingletons());
|
||||
c.inject(new TransportKeyAgreementModule.EagerSingletons());
|
||||
c.inject(new TransportModule.EagerSingletons());
|
||||
c.inject(new ValidationModule.EagerSingletons());
|
||||
c.inject(new VersioningModule.EagerSingletons());
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.briarproject.bramble.settings.SettingsModule;
|
||||
import org.briarproject.bramble.sync.SyncModule;
|
||||
import org.briarproject.bramble.sync.validation.ValidationModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
|
||||
import org.briarproject.bramble.versioning.VersioningModule;
|
||||
|
||||
import dagger.Module;
|
||||
@@ -49,6 +50,7 @@ import dagger.Module;
|
||||
RendezvousModule.class,
|
||||
SettingsModule.class,
|
||||
SyncModule.class,
|
||||
TransportKeyAgreementModule.class,
|
||||
TransportModule.class,
|
||||
ValidationModule.class,
|
||||
VersioningModule.class
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
@NotNullByDefault
|
||||
interface MessageEncoder {
|
||||
|
||||
Message encodeKeyMessage(GroupId contactGroupId,
|
||||
TransportId transportId, PublicKey publicKey);
|
||||
|
||||
Message encodeActivateMessage(GroupId contactGroupId,
|
||||
TransportId transportId, MessageId previousMessageId);
|
||||
|
||||
BdfDictionary encodeMessageMetadata(TransportId transportId,
|
||||
MessageType type, boolean local);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
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 javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
|
||||
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MessageEncoderImpl implements MessageEncoder {
|
||||
|
||||
private final ClientHelper clientHelper;
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
MessageEncoderImpl(ClientHelper clientHelper, Clock clock) {
|
||||
this.clientHelper = clientHelper;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message encodeKeyMessage(GroupId contactGroupId,
|
||||
TransportId transportId, PublicKey publicKey) {
|
||||
BdfList body = BdfList.of(
|
||||
KEY.getValue(),
|
||||
transportId.getString(),
|
||||
publicKey.getEncoded());
|
||||
return encodeMessage(contactGroupId, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message encodeActivateMessage(GroupId contactGroupId,
|
||||
TransportId transportId, MessageId previousMessageId) {
|
||||
BdfList body = BdfList.of(
|
||||
ACTIVATE.getValue(),
|
||||
transportId.getString(),
|
||||
previousMessageId);
|
||||
return encodeMessage(contactGroupId, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary encodeMessageMetadata(TransportId transportId,
|
||||
MessageType type, boolean local) {
|
||||
return BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_IS_SESSION, false),
|
||||
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()),
|
||||
new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue()),
|
||||
new BdfEntry(MSG_KEY_LOCAL, local));
|
||||
}
|
||||
|
||||
private Message encodeMessage(GroupId contactGroupId, BdfList body) {
|
||||
try {
|
||||
return clientHelper.createMessage(contactGroupId,
|
||||
clock.currentTimeMillis(), clientHelper.toByteArray(body));
|
||||
} catch (FormatException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
enum MessageType {
|
||||
|
||||
KEY(0),
|
||||
ACTIVATE(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
MessageType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
static MessageType fromValue(int value) throws FormatException {
|
||||
for (MessageType t : values()) if (t.value == value) return t;
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class Session {
|
||||
|
||||
private final State state;
|
||||
@Nullable
|
||||
private final MessageId lastLocalMessageId;
|
||||
@Nullable
|
||||
private final KeyPair localKeyPair;
|
||||
@Nullable
|
||||
private final Long localTimestamp, remoteTimestamp;
|
||||
@Nullable
|
||||
private final KeySetId keySetId;
|
||||
|
||||
Session(State state, @Nullable MessageId lastLocalMessageId,
|
||||
@Nullable KeyPair localKeyPair,
|
||||
@Nullable Long localTimestamp, @Nullable Long remoteTimestamp,
|
||||
@Nullable KeySetId keySetId) {
|
||||
this.state = state;
|
||||
this.lastLocalMessageId = lastLocalMessageId;
|
||||
this.localKeyPair = localKeyPair;
|
||||
this.localTimestamp = localTimestamp;
|
||||
this.remoteTimestamp = remoteTimestamp;
|
||||
this.keySetId = keySetId;
|
||||
}
|
||||
|
||||
State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
MessageId getLastLocalMessageId() {
|
||||
return lastLocalMessageId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
KeyPair getLocalKeyPair() {
|
||||
return localKeyPair;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Long getLocalTimestamp() {
|
||||
return localTimestamp;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Long getRemoteTimestamp() {
|
||||
return remoteTimestamp;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
KeySetId getKeySetId() {
|
||||
return keySetId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
@NotNullByDefault
|
||||
interface SessionEncoder {
|
||||
|
||||
BdfDictionary encodeSession(Session s, TransportId transportId);
|
||||
|
||||
BdfDictionary getSessionQuery(TransportId transportId);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_REMOTE_TIMESTAMP;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class SessionEncoderImpl implements SessionEncoder {
|
||||
|
||||
@Inject
|
||||
SessionEncoderImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary encodeSession(Session s, TransportId transportId) {
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(MSG_KEY_IS_SESSION, true);
|
||||
meta.put(MSG_KEY_TRANSPORT_ID, transportId.getString());
|
||||
meta.put(SESSION_KEY_STATE, s.getState().getValue());
|
||||
putNullable(meta, SESSION_KEY_LAST_LOCAL_MESSAGE_ID,
|
||||
s.getLastLocalMessageId());
|
||||
KeyPair localKeyPair = s.getLocalKeyPair();
|
||||
if (localKeyPair == null) {
|
||||
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY, NULL_VALUE);
|
||||
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY, NULL_VALUE);
|
||||
} else {
|
||||
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY,
|
||||
localKeyPair.getPublic().getEncoded());
|
||||
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY,
|
||||
localKeyPair.getPrivate().getEncoded());
|
||||
}
|
||||
putNullable(meta, SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp());
|
||||
putNullable(meta, SESSION_KEY_REMOTE_TIMESTAMP, s.getRemoteTimestamp());
|
||||
KeySetId keySetId = s.getKeySetId();
|
||||
if (keySetId == null) meta.put(SESSION_KEY_KEY_SET_ID, NULL_VALUE);
|
||||
else meta.put(SESSION_KEY_KEY_SET_ID, keySetId.getInt());
|
||||
return meta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary getSessionQuery(TransportId transportId) {
|
||||
return BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_IS_SESSION, true),
|
||||
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()));
|
||||
}
|
||||
|
||||
private void putNullable(BdfDictionary meta, String key,
|
||||
@Nullable Object o) {
|
||||
meta.put(key, o == null ? NULL_VALUE : o);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
interface SessionParser {
|
||||
|
||||
Session parseSession(BdfDictionary meta) throws FormatException;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_REMOTE_TIMESTAMP;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class SessionParserImpl implements SessionParser {
|
||||
|
||||
private final TransportKeyAgreementCrypto crypto;
|
||||
|
||||
@Inject
|
||||
SessionParserImpl(TransportKeyAgreementCrypto crypto) {
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session parseSession(BdfDictionary meta) throws FormatException {
|
||||
State state =
|
||||
State.fromValue(meta.getLong(SESSION_KEY_STATE).intValue());
|
||||
|
||||
MessageId lastLocalMessageId = null;
|
||||
byte[] lastLocalMessageIdBytes =
|
||||
meta.getOptionalRaw(SESSION_KEY_LAST_LOCAL_MESSAGE_ID);
|
||||
if (lastLocalMessageIdBytes != null) {
|
||||
lastLocalMessageId = new MessageId(lastLocalMessageIdBytes);
|
||||
}
|
||||
|
||||
KeyPair localKeyPair = null;
|
||||
byte[] localPublicKeyBytes =
|
||||
meta.getOptionalRaw(SESSION_KEY_LOCAL_PUBLIC_KEY);
|
||||
byte[] localPrivateKeyBytes =
|
||||
meta.getOptionalRaw(SESSION_KEY_LOCAL_PRIVATE_KEY);
|
||||
if (localPublicKeyBytes != null && localPrivateKeyBytes != null) {
|
||||
PublicKey pub = crypto.parsePublicKey(localPublicKeyBytes);
|
||||
PrivateKey priv = crypto.parsePrivateKey(localPrivateKeyBytes);
|
||||
localKeyPair = new KeyPair(pub, priv);
|
||||
}
|
||||
|
||||
Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP);
|
||||
Long remoteTimestamp =
|
||||
meta.getOptionalLong(SESSION_KEY_REMOTE_TIMESTAMP);
|
||||
|
||||
KeySetId keySetId = null;
|
||||
Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID);
|
||||
if (keySetIdLong != null) {
|
||||
keySetId = new KeySetId(keySetIdLong.intValue());
|
||||
}
|
||||
|
||||
return new Session(state, lastLocalMessageId, localKeyPair,
|
||||
localTimestamp, remoteTimestamp, keySetId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
enum State {
|
||||
|
||||
/**
|
||||
* We've sent a key message and are awaiting the contact's key message.
|
||||
*/
|
||||
AWAIT_KEY(0),
|
||||
|
||||
/**
|
||||
* We've exchanged key messages, derived the transport keys and sent an
|
||||
* activate message, and now we're awaiting the contact's activate message.
|
||||
*/
|
||||
AWAIT_ACTIVATE(1),
|
||||
|
||||
/**
|
||||
* We've exchanged key messages and activate messages, and have derived and
|
||||
* activated the transport keys. This is the end state.
|
||||
*/
|
||||
ACTIVATED(2);
|
||||
|
||||
private final int value;
|
||||
|
||||
State(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
static State fromValue(int value) throws FormatException {
|
||||
for (State s : values()) if (s.value == value) return s;
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
interface TransportKeyAgreementConstants {
|
||||
|
||||
String MSG_KEY_IS_SESSION = "isSession";
|
||||
String MSG_KEY_MESSAGE_TYPE = "messageType";
|
||||
String MSG_KEY_TRANSPORT_ID = "transportId";
|
||||
String MSG_KEY_PUBLIC_KEY = "publicKey";
|
||||
String MSG_KEY_LOCAL = "local";
|
||||
|
||||
String SESSION_KEY_STATE = "state";
|
||||
String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId";
|
||||
String SESSION_KEY_LOCAL_PUBLIC_KEY = "localPublicKey";
|
||||
String SESSION_KEY_LOCAL_PRIVATE_KEY = "localPrivateKey";
|
||||
String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp";
|
||||
String SESSION_KEY_REMOTE_TIMESTAMP = "remoteTimestamp";
|
||||
String SESSION_KEY_KEY_SET_ID = "keySetId";
|
||||
|
||||
/**
|
||||
* Label for deriving the root key from key pairs.
|
||||
*/
|
||||
String ROOT_KEY_LABEL =
|
||||
"org.briarproject.bramble.transport.agreement/ROOT_KEY";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
@NotNullByDefault
|
||||
interface TransportKeyAgreementCrypto {
|
||||
|
||||
KeyPair generateKeyPair();
|
||||
|
||||
SecretKey deriveRootKey(KeyPair localKeyPair, PublicKey remotePublicKey,
|
||||
long timestamp) throws GeneralSecurityException;
|
||||
|
||||
PublicKey parsePublicKey(byte[] encoded) throws FormatException;
|
||||
|
||||
PrivateKey parsePrivateKey(byte[] encoded) throws FormatException;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.Bytes.compare;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.ROOT_KEY_LABEL;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class TransportKeyAgreementCryptoImpl implements TransportKeyAgreementCrypto {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
@Inject
|
||||
TransportKeyAgreementCryptoImpl(CryptoComponent crypto) {
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair generateKeyPair() {
|
||||
return crypto.generateAgreementKeyPair();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveRootKey(KeyPair localKeyPair,
|
||||
PublicKey remotePublicKey, long timestamp)
|
||||
throws GeneralSecurityException {
|
||||
byte[] theirPublic = remotePublicKey.getEncoded();
|
||||
byte[] ourPublic = localKeyPair.getPublic().getEncoded();
|
||||
boolean alice = compare(ourPublic, theirPublic) < 0;
|
||||
byte[][] inputs = {
|
||||
alice ? ourPublic : theirPublic,
|
||||
alice ? theirPublic : ourPublic
|
||||
};
|
||||
return crypto.deriveSharedSecret(ROOT_KEY_LABEL, remotePublicKey,
|
||||
localKeyPair, inputs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey parsePublicKey(byte[] encoded) throws FormatException {
|
||||
try {
|
||||
return crypto.getAgreementKeyParser().parsePublicKey(encoded);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey parsePrivateKey(byte[] encoded) throws FormatException {
|
||||
try {
|
||||
return crypto.getAgreementKeyParser().parsePrivateKey(encoded);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
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.ContactHook;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
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.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
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.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
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.transport.KeyManager;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.Bytes.compare;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
|
||||
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
|
||||
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
|
||||
import static org.briarproject.bramble.transport.agreement.State.ACTIVATED;
|
||||
import static org.briarproject.bramble.transport.agreement.State.AWAIT_ACTIVATE;
|
||||
import static org.briarproject.bramble.transport.agreement.State.AWAIT_KEY;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook
|
||||
implements TransportKeyAgreementManager, OpenDatabaseHook, ContactHook,
|
||||
ClientVersioningHook {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(TransportKeyAgreementManagerImpl.class.getName());
|
||||
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final ClientVersioningManager clientVersioningManager;
|
||||
private final IdentityManager identityManager;
|
||||
private final KeyManager keyManager;
|
||||
private final MessageEncoder messageEncoder;
|
||||
private final SessionEncoder sessionEncoder;
|
||||
private final SessionParser sessionParser;
|
||||
private final TransportKeyAgreementCrypto crypto;
|
||||
|
||||
private final List<TransportId> transports;
|
||||
private final Group localGroup;
|
||||
|
||||
@Inject
|
||||
TransportKeyAgreementManagerImpl(
|
||||
DatabaseComponent db,
|
||||
ClientHelper clientHelper,
|
||||
MetadataParser metadataParser,
|
||||
ContactGroupFactory contactGroupFactory,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
IdentityManager identityManager,
|
||||
KeyManager keyManager,
|
||||
MessageEncoder messageEncoder,
|
||||
SessionEncoder sessionEncoder,
|
||||
SessionParser sessionParser,
|
||||
TransportKeyAgreementCrypto crypto,
|
||||
PluginConfig config) {
|
||||
super(db, clientHelper, metadataParser);
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.clientVersioningManager = clientVersioningManager;
|
||||
this.identityManager = identityManager;
|
||||
this.keyManager = keyManager;
|
||||
this.messageEncoder = messageEncoder;
|
||||
this.sessionEncoder = sessionEncoder;
|
||||
this.sessionParser = sessionParser;
|
||||
this.crypto = crypto;
|
||||
transports = new ArrayList<>();
|
||||
for (DuplexPluginFactory duplex : config.getDuplexFactories()) {
|
||||
transports.add(duplex.getId());
|
||||
}
|
||||
for (SimplexPluginFactory simplex : config.getSimplexFactories()) {
|
||||
transports.add(simplex.getId());
|
||||
}
|
||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatabaseOpened(Transaction txn) throws DbException {
|
||||
Collection<Contact> contacts = db.getContacts(txn);
|
||||
if (!db.containsGroup(txn, localGroup.getId())) {
|
||||
db.addGroup(txn, localGroup);
|
||||
// Set things up for any pre-existing contacts
|
||||
for (Contact c : contacts) addingContact(txn, c);
|
||||
}
|
||||
// Find any contacts and transports that need keys
|
||||
Map<ContactId, Collection<TransportId>> transportsWithKeys =
|
||||
db.getTransportsWithKeys(txn);
|
||||
for (Contact c : contacts) {
|
||||
Collection<TransportId> withKeys =
|
||||
transportsWithKeys.get(c.getId());
|
||||
for (TransportId t : transports) {
|
||||
if (withKeys == null || !withKeys.contains(t)) {
|
||||
// We need keys for this contact and transport
|
||||
GroupId contactGroupId = getContactGroup(c).getId();
|
||||
SavedSession ss = loadSession(txn, contactGroupId, t);
|
||||
if (ss == null) {
|
||||
// Start a session by sending our key message
|
||||
startSession(txn, contactGroupId, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
// Attach the contact ID to the group
|
||||
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||
// 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
}
|
||||
|
||||
@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 DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
BdfList body, BdfDictionary meta)
|
||||
throws DbException, FormatException {
|
||||
MessageType type = MessageType.fromValue(
|
||||
meta.getLong(MSG_KEY_MESSAGE_TYPE).intValue());
|
||||
TransportId t = new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID));
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Received " + type + " message for " + t);
|
||||
}
|
||||
if (!transports.contains(t)) {
|
||||
// Defer handling the message until we support the transport
|
||||
return DEFER;
|
||||
}
|
||||
SavedSession ss = loadSession(txn, m.getGroupId(), t);
|
||||
if (type == KEY) return handleKeyMessage(txn, t, m, meta, ss);
|
||||
else if (type == ACTIVATE) return handleActivateMessage(txn, t, ss);
|
||||
else throw new AssertionError();
|
||||
}
|
||||
|
||||
private DeliveryAction handleKeyMessage(Transaction txn, TransportId t,
|
||||
Message m, BdfDictionary meta, @Nullable SavedSession ss)
|
||||
throws DbException, FormatException {
|
||||
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
||||
boolean haveKeys = db.containsTransportKeys(txn, c, t);
|
||||
if (ss == null) {
|
||||
if (haveKeys) {
|
||||
// We have keys but no session, so we must have derived keys
|
||||
// when adding the contact. If the contact didn't support
|
||||
// the transport when they added us, they wouldn't have
|
||||
// derived keys at that time. If they later added support for
|
||||
// the transport then they would have started a session, so a
|
||||
// key message is valid in this case
|
||||
return handleKeyMessageForNewSession(txn, c, t, m, meta);
|
||||
} else {
|
||||
// We don't have keys, so we should have created a session at
|
||||
// startup
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} else if (ss.session.getState() == AWAIT_KEY) {
|
||||
if (haveKeys) {
|
||||
// We have keys, so we shouldn't be in the AWAIT_KEY state,
|
||||
// even if the contact didn't derive keys when adding us and
|
||||
// later started a session
|
||||
throw new IllegalStateException();
|
||||
} else {
|
||||
// This is the key message we're waiting for
|
||||
return handleKeyMessageForExistingSession(txn, c, t, m, meta,
|
||||
ss);
|
||||
}
|
||||
} else {
|
||||
return REJECT; // Not valid in this state
|
||||
}
|
||||
}
|
||||
|
||||
private DeliveryAction handleActivateMessage(Transaction txn,
|
||||
TransportId t, @Nullable SavedSession ss) throws DbException {
|
||||
if (ss != null && ss.session.getState() == AWAIT_ACTIVATE) {
|
||||
// Activate the keys and finish the session
|
||||
KeySetId keySetId = requireNonNull(ss.session.getKeySetId());
|
||||
keyManager.activateKeys(txn, singletonMap(t, keySetId));
|
||||
Session session = new Session(ACTIVATED,
|
||||
ss.session.getLastLocalMessageId(), null, null, null, null);
|
||||
saveSession(txn, t, ss.storageId, session);
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
} else {
|
||||
return REJECT; // Not valid in this state
|
||||
}
|
||||
}
|
||||
|
||||
private DeliveryAction handleKeyMessageForNewSession(Transaction txn,
|
||||
ContactId c, TransportId t, Message m, BdfDictionary meta)
|
||||
throws DbException, FormatException {
|
||||
KeyPair localKeyPair = crypto.generateKeyPair();
|
||||
PublicKey remotePublicKey =
|
||||
crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY));
|
||||
Message keyMessage = sendKeyMessage(txn, m.getGroupId(), t,
|
||||
localKeyPair.getPublic());
|
||||
long minTimestamp = min(keyMessage.getTimestamp(), m.getTimestamp());
|
||||
SecretKey rootKey;
|
||||
try {
|
||||
rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey,
|
||||
minTimestamp);
|
||||
} catch (GeneralSecurityException e) {
|
||||
return REJECT; // Invalid public key
|
||||
}
|
||||
boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c));
|
||||
KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey,
|
||||
minTimestamp, alice, false);
|
||||
Message activateMessage =
|
||||
sendActivateMessage(txn, m.getGroupId(), t, keyMessage.getId());
|
||||
Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(),
|
||||
null, null, null, keySetId);
|
||||
saveNewSession(txn, m.getGroupId(), t, session);
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
private DeliveryAction handleKeyMessageForExistingSession(Transaction txn,
|
||||
ContactId c, TransportId t, Message m, BdfDictionary meta,
|
||||
SavedSession ss) throws DbException, FormatException {
|
||||
KeyPair localKeyPair = requireNonNull(ss.session.getLocalKeyPair());
|
||||
PublicKey remotePublicKey =
|
||||
crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY));
|
||||
long localTimestamp = requireNonNull(ss.session.getLocalTimestamp());
|
||||
long minTimestamp = min(localTimestamp, m.getTimestamp());
|
||||
SecretKey rootKey;
|
||||
try {
|
||||
rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey,
|
||||
minTimestamp);
|
||||
} catch (GeneralSecurityException e) {
|
||||
return REJECT; // Invalid public key
|
||||
}
|
||||
boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c));
|
||||
KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey,
|
||||
minTimestamp, alice, false);
|
||||
MessageId previousMessageId =
|
||||
requireNonNull(ss.session.getLastLocalMessageId());
|
||||
Message activateMessage =
|
||||
sendActivateMessage(txn, m.getGroupId(), t, previousMessageId);
|
||||
Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(),
|
||||
null, null, null, keySetId);
|
||||
saveSession(txn, t, ss.storageId, session);
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
private void startSession(Transaction txn, GroupId contactGroupId,
|
||||
TransportId t) throws DbException {
|
||||
KeyPair localKeyPair = crypto.generateKeyPair();
|
||||
Message keyMessage = sendKeyMessage(txn, contactGroupId, t,
|
||||
localKeyPair.getPublic());
|
||||
Session session = new Session(AWAIT_KEY, keyMessage.getId(),
|
||||
localKeyPair, keyMessage.getTimestamp(), null, null);
|
||||
saveNewSession(txn, contactGroupId, t, session);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SavedSession loadSession(Transaction txn, GroupId contactGroupId,
|
||||
TransportId t) throws DbException {
|
||||
try {
|
||||
BdfDictionary query = sessionEncoder.getSessionQuery(t);
|
||||
Collection<MessageId> ids =
|
||||
clientHelper.getMessageIds(txn, contactGroupId, query);
|
||||
if (ids.size() > 1) throw new DbException();
|
||||
if (ids.isEmpty()) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("No session for " + t);
|
||||
return null;
|
||||
}
|
||||
MessageId storageId = ids.iterator().next();
|
||||
BdfDictionary bdfSession =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, storageId);
|
||||
Session session = sessionParser.parseSession(bdfSession);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Loaded session in state " + session.getState()
|
||||
+ " for " + t);
|
||||
}
|
||||
return new SavedSession(session, storageId);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveNewSession(Transaction txn, GroupId contactGroupId,
|
||||
TransportId t, Session session) throws DbException {
|
||||
Message m =
|
||||
clientHelper.createMessageForStoringMetadata(contactGroupId);
|
||||
db.addLocalMessage(txn, m, new Metadata(), false, false);
|
||||
MessageId storageId = m.getId();
|
||||
saveSession(txn, t, storageId, session);
|
||||
}
|
||||
|
||||
private void saveSession(Transaction txn, TransportId t,
|
||||
MessageId storageId, Session session) throws DbException {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Saving session in state " + session.getState()
|
||||
+ " for " + t);
|
||||
}
|
||||
BdfDictionary meta = sessionEncoder.encodeSession(session, t);
|
||||
try {
|
||||
clientHelper.mergeMessageMetadata(txn, storageId, meta);
|
||||
} catch (FormatException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private Message sendKeyMessage(Transaction txn, GroupId contactGroupId,
|
||||
TransportId t, PublicKey publicKey) throws DbException {
|
||||
Message m = messageEncoder.encodeKeyMessage(contactGroupId, t,
|
||||
publicKey);
|
||||
sendMessage(txn, t, m, KEY);
|
||||
return m;
|
||||
}
|
||||
|
||||
private Message sendActivateMessage(Transaction txn,
|
||||
GroupId contactGroupId, TransportId t, MessageId previousMessageId)
|
||||
throws DbException {
|
||||
Message m = messageEncoder.encodeActivateMessage(contactGroupId, t,
|
||||
previousMessageId);
|
||||
sendMessage(txn, t, m, ACTIVATE);
|
||||
return m;
|
||||
}
|
||||
|
||||
private void sendMessage(Transaction txn, TransportId t, Message m,
|
||||
MessageType type) throws DbException {
|
||||
BdfDictionary meta =
|
||||
messageEncoder.encodeMessageMetadata(t, type, true);
|
||||
try {
|
||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||
} catch (FormatException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, c);
|
||||
}
|
||||
|
||||
private boolean isLocalPartyAlice(Transaction txn, Contact c)
|
||||
throws DbException {
|
||||
Author local = identityManager.getLocalAuthor(txn);
|
||||
Author remote = c.getAuthor();
|
||||
return compare(local.getId().getBytes(), remote.getId().getBytes()) < 0;
|
||||
}
|
||||
|
||||
private static class SavedSession {
|
||||
|
||||
private final Session session;
|
||||
private final MessageId storageId;
|
||||
|
||||
private SavedSession(Session session, MessageId storageId) {
|
||||
this.session = session;
|
||||
this.storageId = storageId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
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.transport.agreement.TransportKeyAgreementManager;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.CLIENT_ID;
|
||||
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MINOR_VERSION;
|
||||
|
||||
@Module
|
||||
public class TransportKeyAgreementModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
TransportKeyAgreementManager transportKeyAgreementManager;
|
||||
@Inject
|
||||
TransportKeyAgreementValidator transportKeyAgreementValidator;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
TransportKeyAgreementManager provideTransportKeyAgreementManager(
|
||||
LifecycleManager lifecycleManager,
|
||||
ValidationManager validationManager,
|
||||
ContactManager contactManager,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
TransportKeyAgreementManagerImpl transportKeyAgreementManager) {
|
||||
lifecycleManager.registerOpenDatabaseHook(transportKeyAgreementManager);
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
||||
MAJOR_VERSION, transportKeyAgreementManager);
|
||||
contactManager.registerContactHook(transportKeyAgreementManager);
|
||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||
MINOR_VERSION, transportKeyAgreementManager);
|
||||
return transportKeyAgreementManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
TransportKeyAgreementValidator provideTransportKeyAgreementValidator(
|
||||
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
||||
Clock clock, MessageEncoder messageEncoder,
|
||||
ValidationManager validationManager) {
|
||||
TransportKeyAgreementValidator validator =
|
||||
new TransportKeyAgreementValidator(clientHelper,
|
||||
metadataEncoder, clock, messageEncoder);
|
||||
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||
validator);
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
|
||||
return messageEncoder;
|
||||
}
|
||||
|
||||
@Provides
|
||||
SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
|
||||
return sessionEncoder;
|
||||
}
|
||||
|
||||
@Provides
|
||||
SessionParser provideSessionParser(SessionParserImpl sessionParser) {
|
||||
return sessionParser;
|
||||
}
|
||||
|
||||
@Provides
|
||||
TransportKeyAgreementCrypto provideTransportKeyAgreementCrypto(
|
||||
TransportKeyAgreementCryptoImpl transportKeyAgreementCrypto) {
|
||||
return transportKeyAgreementCrypto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.briarproject.bramble.transport.agreement;
|
||||
|
||||
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.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
|
||||
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
|
||||
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class TransportKeyAgreementValidator extends BdfMessageValidator {
|
||||
|
||||
private final MessageEncoder messageEncoder;
|
||||
|
||||
TransportKeyAgreementValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock,
|
||||
MessageEncoder messageEncoder) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
this.messageEncoder = messageEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
|
||||
if (type == KEY) return validateKeyMessage(body);
|
||||
else if (type == ACTIVATE) return validateActivateMessage(body);
|
||||
else throw new AssertionError();
|
||||
}
|
||||
|
||||
private BdfMessageContext validateKeyMessage(BdfList body)
|
||||
throws FormatException {
|
||||
// Message type, transport ID, public key
|
||||
checkSize(body, 3);
|
||||
String transportId = body.getString(1);
|
||||
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
|
||||
byte[] publicKey = body.getRaw(2);
|
||||
checkLength(publicKey, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||
BdfDictionary meta = messageEncoder.encodeMessageMetadata(
|
||||
new TransportId(transportId), KEY, false);
|
||||
meta.put(MSG_KEY_PUBLIC_KEY, publicKey);
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
|
||||
private BdfMessageContext validateActivateMessage(BdfList body)
|
||||
throws FormatException {
|
||||
// Message type, transport ID, previous message ID
|
||||
checkSize(body, 3);
|
||||
String transportId = body.getString(1);
|
||||
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
|
||||
byte[] previousMessageId = body.getRaw(2);
|
||||
checkLength(previousMessageId, MessageId.LENGTH);
|
||||
BdfDictionary meta = messageEncoder.encodeMessageMetadata(
|
||||
new TransportId(transportId), ACTIVATE, false);
|
||||
MessageId dependency = new MessageId(previousMessageId);
|
||||
return new BdfMessageContext(meta, singletonList(dependency));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user