mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-15 04:18:53 +01:00
Upgrade handshake protocol to new key agreement method.
This commit is contained in:
@@ -54,6 +54,38 @@ public interface CryptoComponent {
|
||||
KeyPair ourKeyPair, byte[]... inputs)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Derives a shared secret from two static and two ephemeral key pairs.
|
||||
* <p>
|
||||
* Do not use this method for new protocols. The shared secret can be
|
||||
* re-derived using the ephemeral public keys and both static private
|
||||
* keys, so keys derived from the shared secret should not be used if
|
||||
* forward secrecy is required. Use {@link #deriveSharedSecret(String,
|
||||
* PublicKey, PublicKey, KeyPair, KeyPair, boolean, byte[]...)} instead.
|
||||
* <p>
|
||||
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||
* <p>
|
||||
*
|
||||
* @param label A namespaced label indicating the purpose of this shared
|
||||
* secret, to prevent it from being repurposed or colliding with a shared
|
||||
* secret derived for another purpose
|
||||
* @param theirStaticPublicKey The static public key of the remote party
|
||||
* @param theirEphemeralPublicKey The ephemeral public key of the remote
|
||||
* party
|
||||
* @param ourStaticKeyPair The static key pair of the local party
|
||||
* @param ourEphemeralKeyPair The ephemeral key pair of the local party
|
||||
* @param alice True if the local party is Alice
|
||||
* @param inputs Additional inputs that will be included in the
|
||||
* derivation of the shared secret
|
||||
* @return The shared secret
|
||||
*/
|
||||
@Deprecated
|
||||
SecretKey deriveSharedSecretBadly(String label,
|
||||
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||
boolean alice, byte[]... inputs)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Derives a shared secret from two static and two ephemeral key pairs.
|
||||
*
|
||||
|
||||
@@ -5,14 +5,31 @@ import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
|
||||
interface HandshakeConstants {
|
||||
|
||||
/**
|
||||
* The current version of the handshake protocol.
|
||||
* The current major version of the handshake protocol.
|
||||
*/
|
||||
byte PROTOCOL_VERSION = 0;
|
||||
|
||||
/**
|
||||
* Label for deriving the master key.
|
||||
* The current minor version of the handshake protocol.
|
||||
*/
|
||||
String MASTER_KEY_LABEL = "org.briarproject.bramble.handshake/MASTER_KEY";
|
||||
byte PROTOCOL_MINOR_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Label for deriving the master key when using the deprecated v0.0 key
|
||||
* derivation method.
|
||||
* <p>
|
||||
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||
*/
|
||||
@Deprecated
|
||||
String MASTER_KEY_LABEL_0_0 =
|
||||
"org.briarproject.bramble.handshake/MASTER_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving the master key when using the v0.1 key derivation
|
||||
* method.
|
||||
*/
|
||||
String MASTER_KEY_LABEL_0_1 =
|
||||
"org.briarproject.bramble.handshake/MASTER_KEY_0_1";
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's proof of ownership from the master key.
|
||||
|
||||
@@ -13,11 +13,26 @@ interface HandshakeCrypto {
|
||||
KeyPair generateEphemeralKeyPair();
|
||||
|
||||
/**
|
||||
* Derives the master key from the given static and ephemeral keys.
|
||||
* Derives the master key from the given static and ephemeral keys using
|
||||
* the deprecated v0.0 key derivation method.
|
||||
* <p>
|
||||
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
|
||||
*
|
||||
* @param alice Whether the local peer is Alice
|
||||
*/
|
||||
SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
||||
@Deprecated
|
||||
SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Derives the master key from the given static and ephemeral keys using
|
||||
* the v0.1 key derivation method.
|
||||
*
|
||||
* @param alice Whether the local peer is Alice
|
||||
*/
|
||||
SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
@@ -13,7 +13,8 @@ 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;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_0;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_1;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -32,7 +33,8 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
||||
@Deprecated
|
||||
public SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice) throws
|
||||
GeneralSecurityException {
|
||||
@@ -46,9 +48,29 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
|
||||
alice ? ourEphemeral : theirEphemeral,
|
||||
alice ? theirEphemeral : ourEphemeral
|
||||
};
|
||||
return crypto.deriveSharedSecret(MASTER_KEY_LABEL, theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair, ourEphemeralKeyPair,
|
||||
alice, inputs);
|
||||
return crypto.deriveSharedSecretBadly(MASTER_KEY_LABEL_0_0,
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveMasterKey_0_1(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_0_1,
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,15 +27,20 @@ import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
|
||||
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;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
|
||||
@Immutable
|
||||
@@ -48,7 +53,9 @@ class HandshakeManagerImpl implements HandshakeManager {
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
private static boolean isKnownRecordType(byte type) {
|
||||
return type == EPHEMERAL_PUBLIC_KEY || type == PROOF_OF_OWNERSHIP;
|
||||
return type == RECORD_TYPE_EPHEMERAL_PUBLIC_KEY ||
|
||||
type == RECORD_TYPE_PROOF_OF_OWNERSHIP ||
|
||||
type == RECORD_TYPE_MINOR_VERSION;
|
||||
}
|
||||
|
||||
private final TransactionManager db;
|
||||
@@ -94,19 +101,31 @@ class HandshakeManagerImpl implements HandshakeManager {
|
||||
.createRecordWriter(out.getOutputStream());
|
||||
KeyPair ourEphemeralKeyPair =
|
||||
handshakeCrypto.generateEphemeralKeyPair();
|
||||
PublicKey theirEphemeralPublicKey;
|
||||
Pair<Byte, PublicKey> theirMinorVersionAndKey;
|
||||
if (alice) {
|
||||
sendMinorVersion(recordWriter);
|
||||
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
||||
theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
|
||||
} else {
|
||||
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
||||
theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
|
||||
sendMinorVersion(recordWriter);
|
||||
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||
}
|
||||
byte theirMinorVersion = theirMinorVersionAndKey.getFirst();
|
||||
PublicKey theirEphemeralPublicKey = theirMinorVersionAndKey.getSecond();
|
||||
SecretKey masterKey;
|
||||
try {
|
||||
masterKey = handshakeCrypto.deriveMasterKey(theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||
ourEphemeralKeyPair, alice);
|
||||
if (theirMinorVersion > 0) {
|
||||
masterKey = handshakeCrypto.deriveMasterKey_0_1(
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice);
|
||||
} else {
|
||||
// TODO: Remove this branch after a reasonable migration
|
||||
// period (added 2023-03-10).
|
||||
masterKey = handshakeCrypto.deriveMasterKey_0_0(
|
||||
theirStaticPublicKey, theirEphemeralPublicKey,
|
||||
ourStaticKeyPair, ourEphemeralKeyPair, alice);
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
@@ -127,34 +146,90 @@ class HandshakeManagerImpl implements HandshakeManager {
|
||||
}
|
||||
|
||||
private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION, EPHEMERAL_PUBLIC_KEY,
|
||||
k.getEncoded()));
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY, k.getEncoded()));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private PublicKey receivePublicKey(RecordReader r) throws IOException {
|
||||
byte[] key = readRecord(r, EPHEMERAL_PUBLIC_KEY).getPayload();
|
||||
/**
|
||||
* Receives the remote peer's protocol minor version and ephemeral public
|
||||
* key.
|
||||
* <p>
|
||||
* In version 0.1 of the protocol, each peer sends a minor version record
|
||||
* followed by an ephemeral public key record.
|
||||
* <p>
|
||||
* In version 0.0 of the protocol, each peer sends an ephemeral public key
|
||||
* record without a preceding minor version record.
|
||||
* <p>
|
||||
* Therefore the remote peer's minor version must be non-zero if a minor
|
||||
* version record is received, and is assumed to be zero if no minor
|
||||
* version record is received.
|
||||
*/
|
||||
private Pair<Byte, PublicKey> receiveMinorVersionAndKey(RecordReader r)
|
||||
throws IOException {
|
||||
byte theirMinorVersion;
|
||||
PublicKey theirEphemeralPublicKey;
|
||||
// The first record can be either a minor version record or an
|
||||
// ephemeral public key record
|
||||
Record first = readRecord(r, asList(RECORD_TYPE_MINOR_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
|
||||
if (first.getRecordType() == RECORD_TYPE_MINOR_VERSION) {
|
||||
// The payload must be a single byte giving the remote peer's
|
||||
// protocol minor version, which must be non-zero
|
||||
byte[] payload = first.getPayload();
|
||||
checkLength(payload, 1);
|
||||
theirMinorVersion = payload[0];
|
||||
if (theirMinorVersion == 0) throw new FormatException();
|
||||
// The second record must be an ephemeral public key record
|
||||
Record second = readRecord(r,
|
||||
singletonList(RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
|
||||
theirEphemeralPublicKey = parsePublicKey(second);
|
||||
} else {
|
||||
// The remote peer did not send a minor version record, so the
|
||||
// remote peer's protocol minor version is assumed to be zero
|
||||
// TODO: Remove this branch after a reasonable migration period
|
||||
// (added 2023-03-10).
|
||||
theirMinorVersion = 0;
|
||||
theirEphemeralPublicKey = parsePublicKey(first);
|
||||
}
|
||||
return new Pair<>(theirMinorVersion, theirEphemeralPublicKey);
|
||||
}
|
||||
|
||||
private PublicKey parsePublicKey(Record rec) throws IOException {
|
||||
if (rec.getRecordType() != RECORD_TYPE_EPHEMERAL_PUBLIC_KEY) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
byte[] key = rec.getPayload();
|
||||
checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||
return new AgreementPublicKey(key);
|
||||
}
|
||||
|
||||
private void sendProof(RecordWriter w, byte[] proof) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION, PROOF_OF_OWNERSHIP, proof));
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION,
|
||||
RECORD_TYPE_PROOF_OF_OWNERSHIP, proof));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private byte[] receiveProof(RecordReader r) throws IOException {
|
||||
byte[] proof = readRecord(r, PROOF_OF_OWNERSHIP).getPayload();
|
||||
Record rec = readRecord(r,
|
||||
singletonList(RECORD_TYPE_PROOF_OF_OWNERSHIP));
|
||||
byte[] proof = rec.getPayload();
|
||||
checkLength(proof, PROOF_BYTES, PROOF_BYTES);
|
||||
return proof;
|
||||
}
|
||||
|
||||
private Record readRecord(RecordReader r, byte expectedType)
|
||||
private void sendMinorVersion(RecordWriter w) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION, RECORD_TYPE_MINOR_VERSION,
|
||||
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private Record readRecord(RecordReader r, List<Byte> expectedTypes)
|
||||
throws IOException {
|
||||
// Accept records with current protocol version, expected types only
|
||||
RecordPredicate accept = rec ->
|
||||
rec.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
rec.getRecordType() == expectedType;
|
||||
expectedTypes.contains(rec.getRecordType());
|
||||
Record rec = r.readRecord(accept, IGNORE);
|
||||
if (rec == null) throw new EOFException();
|
||||
return rec;
|
||||
|
||||
@@ -5,7 +5,9 @@ package org.briarproject.bramble.contact;
|
||||
*/
|
||||
interface HandshakeRecordTypes {
|
||||
|
||||
byte EPHEMERAL_PUBLIC_KEY = 0;
|
||||
byte RECORD_TYPE_EPHEMERAL_PUBLIC_KEY = 0;
|
||||
|
||||
byte PROOF_OF_OWNERSHIP = 1;
|
||||
byte RECORD_TYPE_PROOF_OF_OWNERSHIP = 1;
|
||||
|
||||
byte RECORD_TYPE_MINOR_VERSION = 2;
|
||||
}
|
||||
|
||||
@@ -222,7 +222,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveSharedSecret(String label,
|
||||
@Deprecated
|
||||
public SecretKey deriveSharedSecretBadly(String label,
|
||||
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||
boolean alice, byte[]... inputs) throws GeneralSecurityException {
|
||||
@@ -250,6 +251,35 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return new SecretKey(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveSharedSecret(String label,
|
||||
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
|
||||
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
|
||||
boolean alice, byte[]... inputs) throws GeneralSecurityException {
|
||||
PrivateKey ourStaticPrivateKey = ourStaticKeyPair.getPrivate();
|
||||
PrivateKey ourEphemeralPrivateKey = ourEphemeralKeyPair.getPrivate();
|
||||
byte[][] hashInputs = new byte[inputs.length + 3][];
|
||||
// Alice ephemeral/Bob ephemeral
|
||||
hashInputs[0] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||
theirEphemeralPublicKey);
|
||||
// Alice static/Bob ephemeral, Bob static/Alice ephemeral
|
||||
if (alice) {
|
||||
hashInputs[1] = performRawKeyAgreement(ourStaticPrivateKey,
|
||||
theirEphemeralPublicKey);
|
||||
hashInputs[2] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||
theirStaticPublicKey);
|
||||
} else {
|
||||
hashInputs[1] = performRawKeyAgreement(ourEphemeralPrivateKey,
|
||||
theirStaticPublicKey);
|
||||
hashInputs[2] = performRawKeyAgreement(ourStaticPrivateKey,
|
||||
theirEphemeralPublicKey);
|
||||
}
|
||||
arraycopy(inputs, 0, hashInputs, 3, inputs.length);
|
||||
byte[] hash = hash(label, hashInputs);
|
||||
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
|
||||
return new SecretKey(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
|
||||
throws GeneralSecurityException {
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
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.crypto.TransportCrypto;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
|
||||
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 org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.test.PredicateMatcher;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getPendingContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class HandshakeManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final TransactionManager db =
|
||||
context.mock(TransactionManager.class);
|
||||
private final IdentityManager identityManager =
|
||||
context.mock(IdentityManager.class);
|
||||
private final ContactManager contactManager =
|
||||
context.mock(ContactManager.class);
|
||||
private final TransportCrypto transportCrypto =
|
||||
context.mock(TransportCrypto.class);
|
||||
private final HandshakeCrypto handshakeCrypto =
|
||||
context.mock(HandshakeCrypto.class);
|
||||
private final RecordReaderFactory recordReaderFactory =
|
||||
context.mock(RecordReaderFactory.class);
|
||||
private final RecordWriterFactory recordWriterFactory =
|
||||
context.mock(RecordWriterFactory.class);
|
||||
private final RecordReader recordReader = context.mock(RecordReader.class);
|
||||
private final RecordWriter recordWriter = context.mock(RecordWriter.class);
|
||||
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
|
||||
|
||||
private final PendingContact pendingContact = getPendingContact();
|
||||
private final PublicKey theirStaticPublicKey =
|
||||
pendingContact.getPublicKey();
|
||||
private final PublicKey ourStaticPublicKey = getAgreementPublicKey();
|
||||
private final PrivateKey ourStaticPrivateKey = getAgreementPrivateKey();
|
||||
private final KeyPair ourStaticKeyPair =
|
||||
new KeyPair(ourStaticPublicKey, ourStaticPrivateKey);
|
||||
private final PublicKey theirEphemeralPublicKey = getAgreementPublicKey();
|
||||
private final PublicKey ourEphemeralPublicKey = getAgreementPublicKey();
|
||||
private final PrivateKey ourEphemeralPrivateKey = getAgreementPrivateKey();
|
||||
private final KeyPair ourEphemeralKeyPair =
|
||||
new KeyPair(ourEphemeralPublicKey, ourEphemeralPrivateKey);
|
||||
private final SecretKey masterKey = getSecretKey();
|
||||
private final byte[] ourProof = getRandomBytes(PROOF_BYTES);
|
||||
private final byte[] theirProof = getRandomBytes(PROOF_BYTES);
|
||||
|
||||
private final InputStream in = new ByteArrayInputStream(new byte[0]);
|
||||
private final OutputStream out = new ByteArrayOutputStream(0);
|
||||
|
||||
private final HandshakeManagerImpl handshakeManager =
|
||||
new HandshakeManagerImpl(db, identityManager, contactManager,
|
||||
transportCrypto, handshakeCrypto, recordReaderFactory,
|
||||
recordWriterFactory);
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsAliceWithPeerVersion_0_1() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_1(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsBobWithPeerVersion_0_1() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_1(false);
|
||||
}
|
||||
|
||||
private void testHandshakeWithPeerVersion_0_1(boolean alice)
|
||||
throws Exception {
|
||||
expectPrepareForHandshake(alice);
|
||||
expectSendMinorVersion();
|
||||
expectSendKey();
|
||||
// Remote peer sends minor version, so use new key derivation
|
||||
expectReceiveMinorVersion();
|
||||
expectReceiveKey();
|
||||
expectDeriveMasterKey_0_1(alice);
|
||||
expectDeriveProof(alice);
|
||||
expectSendProof();
|
||||
expectReceiveProof();
|
||||
expectSendEof();
|
||||
expectReceiveEof();
|
||||
expectVerifyOwnership(alice, true);
|
||||
|
||||
HandshakeResult result = handshakeManager.handshake(
|
||||
pendingContact.getId(), in, streamWriter);
|
||||
|
||||
assertArrayEquals(masterKey.getBytes(),
|
||||
result.getMasterKey().getBytes());
|
||||
assertEquals(alice, result.isAlice());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsAliceWithPeerVersion_0_0() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_0(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeAsBobWithPeerVersion_0_0() throws Exception {
|
||||
testHandshakeWithPeerVersion_0_0(false);
|
||||
}
|
||||
|
||||
private void testHandshakeWithPeerVersion_0_0(boolean alice)
|
||||
throws Exception {
|
||||
expectPrepareForHandshake(alice);
|
||||
expectSendMinorVersion();
|
||||
expectSendKey();
|
||||
// Remote peer does not send minor version, so use old key derivation
|
||||
expectReceiveKey();
|
||||
expectDeriveMasterKey_0_0(alice);
|
||||
expectDeriveProof(alice);
|
||||
expectSendProof();
|
||||
expectReceiveProof();
|
||||
expectSendEof();
|
||||
expectReceiveEof();
|
||||
expectVerifyOwnership(alice, true);
|
||||
|
||||
HandshakeResult result = handshakeManager.handshake(
|
||||
pendingContact.getId(), in, streamWriter);
|
||||
|
||||
assertArrayEquals(masterKey.getBytes(),
|
||||
result.getMasterKey().getBytes());
|
||||
assertEquals(alice, result.isAlice());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testProofOfOwnershipNotVerifiedAsAlice() throws Exception {
|
||||
testProofOfOwnershipNotVerified(true);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testProofOfOwnershipNotVerifiedAsBob() throws Exception {
|
||||
testProofOfOwnershipNotVerified(false);
|
||||
}
|
||||
|
||||
private void testProofOfOwnershipNotVerified(boolean alice)
|
||||
throws Exception {
|
||||
expectPrepareForHandshake(alice);
|
||||
expectSendMinorVersion();
|
||||
expectSendKey();
|
||||
expectReceiveMinorVersion();
|
||||
expectReceiveKey();
|
||||
expectDeriveMasterKey_0_1(alice);
|
||||
expectDeriveProof(alice);
|
||||
expectSendProof();
|
||||
expectReceiveProof();
|
||||
expectSendEof();
|
||||
expectReceiveEof();
|
||||
expectVerifyOwnership(alice, false);
|
||||
|
||||
handshakeManager.handshake(pendingContact.getId(), in, streamWriter);
|
||||
}
|
||||
|
||||
private void expectPrepareForHandshake(boolean alice) throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(contactManager).getPendingContact(txn,
|
||||
pendingContact.getId());
|
||||
will(returnValue(pendingContact));
|
||||
oneOf(identityManager).getHandshakeKeys(txn);
|
||||
will(returnValue(ourStaticKeyPair));
|
||||
oneOf(transportCrypto).isAlice(theirStaticPublicKey,
|
||||
ourStaticKeyPair);
|
||||
will(returnValue(alice));
|
||||
oneOf(recordReaderFactory).createRecordReader(in);
|
||||
will(returnValue(recordReader));
|
||||
oneOf(streamWriter).getOutputStream();
|
||||
will(returnValue(out));
|
||||
oneOf(recordWriterFactory).createRecordWriter(out);
|
||||
will(returnValue(recordWriter));
|
||||
oneOf(handshakeCrypto).generateEphemeralKeyPair();
|
||||
will(returnValue(ourEphemeralKeyPair));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectSendMinorVersion() throws Exception {
|
||||
expectWriteRecord(new Record(PROTOCOL_VERSION,
|
||||
RECORD_TYPE_MINOR_VERSION,
|
||||
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||
}
|
||||
|
||||
private void expectReceiveMinorVersion() throws Exception {
|
||||
expectReadRecord(new Record(PROTOCOL_VERSION,
|
||||
RECORD_TYPE_MINOR_VERSION,
|
||||
new byte[] {PROTOCOL_MINOR_VERSION}));
|
||||
}
|
||||
|
||||
private void expectSendKey() throws Exception {
|
||||
expectWriteRecord(new Record(PROTOCOL_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
|
||||
ourEphemeralPublicKey.getEncoded()));
|
||||
}
|
||||
|
||||
private void expectReceiveKey() throws Exception {
|
||||
expectReadRecord(new Record(PROTOCOL_VERSION,
|
||||
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
|
||||
theirEphemeralPublicKey.getEncoded()));
|
||||
}
|
||||
|
||||
private void expectDeriveMasterKey_0_1(boolean alice) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).deriveMasterKey_0_1(theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||
ourEphemeralKeyPair, alice);
|
||||
will(returnValue(masterKey));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectDeriveMasterKey_0_0(boolean alice) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).deriveMasterKey_0_0(theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||
ourEphemeralKeyPair, alice);
|
||||
will(returnValue(masterKey));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectDeriveProof(boolean alice) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).proveOwnership(masterKey, alice);
|
||||
will(returnValue(ourProof));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectSendProof() throws Exception {
|
||||
expectWriteRecord(new Record(PROTOCOL_VERSION,
|
||||
RECORD_TYPE_PROOF_OF_OWNERSHIP, ourProof));
|
||||
}
|
||||
|
||||
private void expectReceiveProof() throws Exception {
|
||||
expectReadRecord(new Record(PROTOCOL_VERSION,
|
||||
RECORD_TYPE_PROOF_OF_OWNERSHIP, theirProof));
|
||||
}
|
||||
|
||||
private void expectSendEof() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(streamWriter).sendEndOfStream();
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectReceiveEof() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(null));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectVerifyOwnership(boolean alice, boolean verified) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(handshakeCrypto).verifyOwnership(masterKey, !alice,
|
||||
theirProof);
|
||||
will(returnValue(verified));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectWriteRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(recordWriter).writeRecord(with(new PredicateMatcher<>(
|
||||
Record.class, r -> recordEquals(record, r))));
|
||||
oneOf(recordWriter).flush();
|
||||
}});
|
||||
}
|
||||
|
||||
private boolean recordEquals(Record expected, Record actual) {
|
||||
return expected.getProtocolVersion() == actual.getProtocolVersion() &&
|
||||
expected.getRecordType() == actual.getRecordType() &&
|
||||
Arrays.equals(expected.getPayload(), actual.getPayload());
|
||||
}
|
||||
|
||||
private void expectReadRecord(Record record) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Test that the `accept` predicate passed to the reader would
|
||||
// accept the expected record
|
||||
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
|
||||
RecordPredicate.class, rp -> rp.test(record))),
|
||||
with(any(RecordPredicate.class)));
|
||||
will(returnValue(record));
|
||||
}});
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,22 @@ public class KeyAgreementTest extends BrambleTestCase {
|
||||
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivesStaticEphemeralSharedSecretBadly() throws Exception {
|
||||
String label = getRandomString(123);
|
||||
KeyPair aStatic = crypto.generateAgreementKeyPair();
|
||||
KeyPair aEphemeral = crypto.generateAgreementKeyPair();
|
||||
KeyPair bStatic = crypto.generateAgreementKeyPair();
|
||||
KeyPair bEphemeral = crypto.generateAgreementKeyPair();
|
||||
SecretKey aShared = crypto.deriveSharedSecretBadly(label,
|
||||
bStatic.getPublic(), bEphemeral.getPublic(), aStatic,
|
||||
aEphemeral, true, inputs);
|
||||
SecretKey bShared = crypto.deriveSharedSecretBadly(label,
|
||||
aStatic.getPublic(), aEphemeral.getPublic(), bStatic,
|
||||
bEphemeral, false, inputs);
|
||||
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivesStaticEphemeralSharedSecret() throws Exception {
|
||||
String label = getRandomString(123);
|
||||
|
||||
Reference in New Issue
Block a user