mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 13:49:53 +01:00
Implement handshake manager.
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
package org.briarproject.bramble.api.contact;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface HandshakeManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handshakes and exchanges pseudonyms with the given pending contact,
|
||||||
|
* converts the pending contact to a contact and returns the contact.
|
||||||
|
*
|
||||||
|
* @param in An incoming stream for the handshake, which must be secured in
|
||||||
|
* handshake mode
|
||||||
|
* @param out An outgoing stream for the handshake, which must be secured
|
||||||
|
* in handshake mode
|
||||||
|
* @param conn The connection to use for the handshake and contact exchange
|
||||||
|
*/
|
||||||
|
Contact handshakeAndAddContact(PendingContactId p,
|
||||||
|
InputStream in, StreamWriter out, DuplexTransportConnection conn)
|
||||||
|
throws DbException, IOException;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.contact;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.contact.HandshakeManager;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -40,4 +41,17 @@ public class ContactModule {
|
|||||||
ContactExchangeCryptoImpl contactExchangeCrypto) {
|
ContactExchangeCryptoImpl contactExchangeCrypto) {
|
||||||
return contactExchangeCrypto;
|
return contactExchangeCrypto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
HandshakeManager provideHandshakeManager(
|
||||||
|
HandshakeManagerImpl handshakeManager) {
|
||||||
|
return handshakeManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
HandshakeCrypto provideHandshakeCrypto(
|
||||||
|
HandshakeCryptoImpl handshakeCrypto) {
|
||||||
|
return handshakeCrypto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
|
interface HandshakeConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current version of the handshake protocol.
|
||||||
|
*/
|
||||||
|
byte PROTOCOL_VERSION = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for deriving the master key.
|
||||||
|
*/
|
||||||
|
String MASTER_KEY_LABEL = "org.briarproject.bramble.handshake/MASTER_KEY";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for deriving Alice's proof of ownership from the master key.
|
||||||
|
*/
|
||||||
|
String ALICE_PROOF_LABEL = "org.briarproject.bramble.handshake/ALICE_PROOF";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for deriving Bob's proof of ownership from the master key.
|
||||||
|
*/
|
||||||
|
String BOB_PROOF_LABEL = "org.briarproject.bramble.handshake/BOB_PROOF";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
|
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.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface HandshakeCrypto {
|
||||||
|
|
||||||
|
boolean isLocalPeerAlice(PublicKey theirStaticPublicKey,
|
||||||
|
KeyPair ourStaticKeyPair);
|
||||||
|
|
||||||
|
KeyPair generateEphemeralKeyPair();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives the master key from the given static and ephemeral keys.
|
||||||
|
*
|
||||||
|
* @param alice Whether the local peer is Alice
|
||||||
|
*/
|
||||||
|
SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
||||||
|
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||||
|
KeyPair ourEphemeralKeyPair, boolean alice)
|
||||||
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns proof that the local peer knows the master key and therefore
|
||||||
|
* owns the static and ephemeral public keys sent by the local peer.
|
||||||
|
*
|
||||||
|
* @param alice Whether the proof is being created by Alice
|
||||||
|
*/
|
||||||
|
byte[] proveOwnership(SecretKey masterKey, boolean alice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the given proof that the remote peer knows the master key and
|
||||||
|
* therefore owns the static and ephemeral keys sent by the remote peer.
|
||||||
|
*
|
||||||
|
* @param alice Whether the proof was created by Alice
|
||||||
|
*/
|
||||||
|
boolean verifyOwnership(SecretKey masterKey, boolean alice, byte[] proof);
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Bytes;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
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.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class HandshakeCryptoImpl implements HandshakeCrypto {
|
||||||
|
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HandshakeCryptoImpl(CryptoComponent crypto) {
|
||||||
|
this.crypto = crypto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocalPeerAlice(PublicKey theirStaticPublicKey,
|
||||||
|
KeyPair ourStaticKeyPair) {
|
||||||
|
byte[] ours = ourStaticKeyPair.getPublic().getEncoded();
|
||||||
|
byte[] theirs = theirStaticPublicKey.getEncoded();
|
||||||
|
return new Bytes(ours).compareTo(new Bytes(theirs)) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyPair generateEphemeralKeyPair() {
|
||||||
|
return crypto.generateAgreementKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
||||||
|
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||||
|
KeyPair ourEphemeralKeyPair, boolean alice) throws
|
||||||
|
GeneralSecurityException {
|
||||||
|
byte[] theirStatic = theirStaticPublicKey.getEncoded();
|
||||||
|
byte[] theirEphemeral = theirEphemeralPublicKey.getEncoded();
|
||||||
|
byte[] ourStatic = ourStaticKeyPair.getPublic().getEncoded();
|
||||||
|
byte[] ourEphemeral = ourEphemeralKeyPair.getPublic().getEncoded();
|
||||||
|
byte[][] inputs = {
|
||||||
|
alice ? ourStatic : theirStatic,
|
||||||
|
alice ? theirStatic : ourStatic,
|
||||||
|
alice ? ourEphemeral : theirEphemeral,
|
||||||
|
alice ? theirEphemeral : ourEphemeral
|
||||||
|
};
|
||||||
|
return crypto.deriveSharedSecret(MASTER_KEY_LABEL, theirStaticPublicKey,
|
||||||
|
theirEphemeralPublicKey, ourStaticKeyPair, ourEphemeralKeyPair,
|
||||||
|
alice, inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] proveOwnership(SecretKey masterKey, boolean alice) {
|
||||||
|
String label = alice ? ALICE_PROOF_LABEL : BOB_PROOF_LABEL;
|
||||||
|
return crypto.mac(label, masterKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verifyOwnership(SecretKey masterKey, boolean alice,
|
||||||
|
byte[] proof) {
|
||||||
|
String label = alice ? ALICE_PROOF_LABEL : BOB_PROOF_LABEL;
|
||||||
|
return crypto.verifyMac(proof, label, masterKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.Pair;
|
||||||
|
import org.briarproject.bramble.api.Predicate;
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.contact.HandshakeManager;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||||
|
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||||
|
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.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.record.Record;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_VERSION;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeRecordTypes.EPHEMERAL_PUBLIC_KEY;
|
||||||
|
import static org.briarproject.bramble.contact.HandshakeRecordTypes.PROOF_OF_OWNERSHIP;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class HandshakeManagerImpl implements HandshakeManager {
|
||||||
|
|
||||||
|
// Ignore records with current protocol version, unknown record type
|
||||||
|
private static final Predicate<Record> IGNORE = r ->
|
||||||
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
|
!isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
|
private static boolean isKnownRecordType(byte type) {
|
||||||
|
return type == EPHEMERAL_PUBLIC_KEY || type == PROOF_OF_OWNERSHIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final TransactionManager db;
|
||||||
|
private final IdentityManager identityManager;
|
||||||
|
private final ContactManager contactManager;
|
||||||
|
private final HandshakeCrypto handshakeCrypto;
|
||||||
|
private final RecordReaderFactory recordReaderFactory;
|
||||||
|
private final RecordWriterFactory recordWriterFactory;
|
||||||
|
private final ContactExchangeManager contactExchangeManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HandshakeManagerImpl(DatabaseComponent db,
|
||||||
|
IdentityManager identityManager,
|
||||||
|
ContactManager contactManager,
|
||||||
|
HandshakeCrypto handshakeCrypto,
|
||||||
|
RecordReaderFactory recordReaderFactory,
|
||||||
|
RecordWriterFactory recordWriterFactory,
|
||||||
|
ContactExchangeManager contactExchangeManager) {
|
||||||
|
this.db = db;
|
||||||
|
this.identityManager = identityManager;
|
||||||
|
this.contactManager = contactManager;
|
||||||
|
this.handshakeCrypto = handshakeCrypto;
|
||||||
|
this.recordReaderFactory = recordReaderFactory;
|
||||||
|
this.recordWriterFactory = recordWriterFactory;
|
||||||
|
this.contactExchangeManager = contactExchangeManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Contact handshakeAndAddContact(PendingContactId p,
|
||||||
|
InputStream in, StreamWriter out, DuplexTransportConnection conn)
|
||||||
|
throws DbException, IOException {
|
||||||
|
Pair<PublicKey, KeyPair> keys = db.transactionWithResult(true, txn -> {
|
||||||
|
PendingContact pendingContact =
|
||||||
|
contactManager.getPendingContact(txn, p);
|
||||||
|
KeyPair keyPair = identityManager.getHandshakeKeys(txn);
|
||||||
|
return new Pair<>(pendingContact.getPublicKey(), keyPair);
|
||||||
|
});
|
||||||
|
PublicKey theirStaticPublicKey = keys.getFirst();
|
||||||
|
KeyPair ourStaticKeyPair = keys.getSecond();
|
||||||
|
boolean alice = handshakeCrypto.isLocalPeerAlice(theirStaticPublicKey,
|
||||||
|
ourStaticKeyPair);
|
||||||
|
RecordReader recordReader = recordReaderFactory.createRecordReader(in);
|
||||||
|
RecordWriter recordWriter = recordWriterFactory
|
||||||
|
.createRecordWriter(out.getOutputStream());
|
||||||
|
KeyPair ourEphemeralKeyPair =
|
||||||
|
handshakeCrypto.generateEphemeralKeyPair();
|
||||||
|
PublicKey theirEphemeralPublicKey;
|
||||||
|
if (alice) {
|
||||||
|
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||||
|
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
||||||
|
} else {
|
||||||
|
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
||||||
|
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||||
|
}
|
||||||
|
SecretKey masterKey;
|
||||||
|
try {
|
||||||
|
masterKey = handshakeCrypto.deriveMasterKey(theirStaticPublicKey,
|
||||||
|
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||||
|
ourEphemeralKeyPair, alice);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
byte[] ourProof = handshakeCrypto.proveOwnership(masterKey, alice);
|
||||||
|
byte[] theirProof;
|
||||||
|
if (alice) {
|
||||||
|
sendProof(recordWriter, ourProof);
|
||||||
|
theirProof = receiveProof(recordReader);
|
||||||
|
} else {
|
||||||
|
theirProof = receiveProof(recordReader);
|
||||||
|
sendProof(recordWriter, ourProof);
|
||||||
|
}
|
||||||
|
out.sendEndOfStream();
|
||||||
|
recordReader.readRecord(r -> false, IGNORE);
|
||||||
|
if (!handshakeCrypto.verifyOwnership(masterKey, !alice, theirProof))
|
||||||
|
throw new FormatException();
|
||||||
|
return contactExchangeManager.exchangeContacts(p, conn, masterKey,
|
||||||
|
alice, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException {
|
||||||
|
w.writeRecord(new Record(PROTOCOL_VERSION, EPHEMERAL_PUBLIC_KEY,
|
||||||
|
k.getEncoded()));
|
||||||
|
w.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PublicKey receivePublicKey(RecordReader r) throws IOException {
|
||||||
|
Record rec = readRecord(r, EPHEMERAL_PUBLIC_KEY);
|
||||||
|
int length = rec.getPayload().length;
|
||||||
|
if (length == 0 || length > MAX_AGREEMENT_PUBLIC_KEY_BYTES)
|
||||||
|
throw new FormatException();
|
||||||
|
return new AgreementPublicKey(rec.getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendProof(RecordWriter w, byte[] proof) throws IOException {
|
||||||
|
w.writeRecord(new Record(PROTOCOL_VERSION, PROOF_OF_OWNERSHIP, proof));
|
||||||
|
w.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] receiveProof(RecordReader r) throws IOException {
|
||||||
|
return readRecord(r, PROOF_OF_OWNERSHIP).getPayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Record readRecord(RecordReader r, byte expectedType)
|
||||||
|
throws IOException {
|
||||||
|
// Accept records with current protocol version, expected type only
|
||||||
|
Predicate<Record> accept = rec ->
|
||||||
|
rec.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
|
rec.getRecordType() == expectedType;
|
||||||
|
Record rec = r.readRecord(accept, IGNORE);
|
||||||
|
if (rec == null) throw new EOFException();
|
||||||
|
return rec;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record types for the handshake protocol.
|
||||||
|
*/
|
||||||
|
interface HandshakeRecordTypes {
|
||||||
|
|
||||||
|
byte EPHEMERAL_PUBLIC_KEY = 0;
|
||||||
|
|
||||||
|
byte PROOF_OF_OWNERSHIP = 1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user