mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +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.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.HandshakeManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -40,4 +41,17 @@ public class ContactModule {
|
||||
ContactExchangeCryptoImpl 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