Implement handshake manager.

This commit is contained in:
akwizgran
2019-05-27 14:54:05 +01:00
parent 60155f146a
commit 24f1b7eeca
7 changed files with 360 additions and 0 deletions

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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";
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}