From d57102ed909da731021c8b1e2ac50adf5d2e8c68 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Sat, 21 Apr 2018 17:21:38 -0300
Subject: [PATCH] IntroductionCrypto: Create dedicated class to handle
 introduction related crypto

---
 .../introduction2/IntroductionConstants.java  |  16 ++
 .../introduction2/IntroductionCrypto.java     |  88 +++++++
 .../introduction2/IntroductionCryptoImpl.java | 216 ++++++++++++++++++
 .../IntroductionCryptoImplTest.java           | 142 ++++++++++++
 .../introduction2/IntroductionCryptoTest.java |  46 ++++
 .../test/BriarIntegrationTestComponent.java   |   2 +
 6 files changed, 510 insertions(+)
 create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java
 create mode 100644 briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java
 create mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java
 create mode 100644 briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java

diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java
index 6b91b33c02..962bd0a52b 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction2/IntroductionConstants.java
@@ -10,4 +10,20 @@ public interface IntroductionConstants {
 	 */
 	int MAX_REQUEST_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
 
+	String LABEL_SESSION_ID = "org.briarproject.briar.introduction/SESSION_ID";
+
+	String LABEL_MASTER_KEY = "org.briarproject.briar.introduction/MASTER_KEY";
+
+	String LABEL_ALICE_MAC_KEY =
+			"org.briarproject.briar.introduction/ALICE_MAC_KEY";
+
+	String LABEL_BOB_MAC_KEY =
+			"org.briarproject.briar.introduction/BOB_MAC_KEY";
+
+	String LABEL_AUTH_MAC = "org.briarproject.briar.introduction/AUTH_MAC";
+
+	String LABEL_AUTH_SIGN = "org.briarproject.briar.introduction/AUTH_SIGN";
+
+	String LABEL_AUTH_NONCE = "org.briarproject.briar.introduction/AUTH_NONCE";
+
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java
new file mode 100644
index 0000000000..a91184db2a
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCrypto.java
@@ -0,0 +1,88 @@
+package org.briarproject.briar.introduction2;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.crypto.KeyPair;
+import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorId;
+import org.briarproject.bramble.api.identity.LocalAuthor;
+import org.briarproject.briar.api.client.SessionId;
+
+import java.security.GeneralSecurityException;
+
+interface IntroductionCrypto {
+
+	/**
+	 * Returns the {@link SessionId} based on the introducer
+	 * and the two introducees.
+	 *
+	 * Note: The roles of Alice and Bob can be switched.
+	 */
+	SessionId getSessionId(Author introducer, Author alice, Author bob);
+
+	/**
+	 * Returns true if the first author is indeed alice
+	 */
+	boolean isAlice(AuthorId alice, AuthorId bob);
+
+	/**
+	 * Generates an agreement key pair.
+	 */
+	KeyPair generateKeyPair();
+
+	/**
+	 * Derives a session master key for Alice or Bob.
+	 *
+	 * @param alice true if the session owner is Alice
+	 * @return The secret master key
+	 */
+	SecretKey deriveMasterKey(IntroduceeSession s, boolean alice)
+			throws GeneralSecurityException;
+
+	/**
+	 * Derives a MAC key from the session's master key for Alice or Bob.
+	 *
+	 * @param masterKey The key returned by {@link #deriveMasterKey(IntroduceeSession, boolean)}
+	 * @param alice true for Alice's MAC key, false for Bob's
+	 * @return The MAC key
+	 */
+	SecretKey deriveMacKey(SecretKey masterKey, boolean alice);
+
+	/**
+	 * Generates a MAC that covers both introducee's ephemeral public keys and
+	 * transport properties.
+	 */
+	byte[] mac(SecretKey macKey, IntroduceeSession s, AuthorId localAuthorId,
+			boolean alice) throws FormatException;
+
+	/**
+	 * Verifies a received MAC
+	 *
+	 * @param mac The MAC to verify
+	 * as returned by {@link #deriveMasterKey(IntroduceeSession, boolean)}
+	 * @throws GeneralSecurityException if the verification fails
+	 */
+	void verifyMac(byte[] mac, IntroduceeSession s, AuthorId localAuthorId)
+			throws GeneralSecurityException, FormatException;
+
+	/**
+	 * Signs a nonce derived from the macKey
+	 * with the local introducee's identity private key.
+	 *
+	 * @param macKey The corresponding MAC key for the signer's role
+	 * @param privateKey The identity private key
+	 * (from {@link LocalAuthor#getPrivateKey()})
+	 * @return The signature as a byte array
+	 */
+	byte[] sign(SecretKey macKey, byte[] privateKey)
+			throws GeneralSecurityException;
+
+	/**
+	 * Verifies the signature on a corresponding MAC key.
+	 *
+	 * @throws GeneralSecurityException if the signature is invalid
+	 */
+	void verifySignature(byte[] signature, IntroduceeSession s,
+			AuthorId localAuthorId) throws GeneralSecurityException;
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java
new file mode 100644
index 0000000000..93ff9b33e3
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction2/IntroductionCryptoImpl.java
@@ -0,0 +1,216 @@
+package org.briarproject.briar.introduction2;
+
+import org.briarproject.bramble.api.Bytes;
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.client.ClientHelper;
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.crypto.KeyPair;
+import org.briarproject.bramble.api.crypto.KeyParser;
+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.data.BdfList;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorId;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.plugin.TransportId;
+import org.briarproject.bramble.api.properties.TransportProperties;
+import org.briarproject.briar.api.client.SessionId;
+
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.annotation.concurrent.Immutable;
+import javax.inject.Inject;
+
+import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_ALICE_MAC_KEY;
+import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_MAC;
+import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_NONCE;
+import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_AUTH_SIGN;
+import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_BOB_MAC_KEY;
+import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_MASTER_KEY;
+import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID;
+import static org.briarproject.briar.api.introduction2.IntroductionManager.CLIENT_VERSION;
+
+@Immutable
+@NotNullByDefault
+class IntroductionCryptoImpl implements IntroductionCrypto {
+
+	private final CryptoComponent crypto;
+	private final ClientHelper clientHelper;
+
+	@Inject
+	IntroductionCryptoImpl(
+			CryptoComponent crypto,
+			ClientHelper clientHelper) {
+		this.crypto = crypto;
+		this.clientHelper = clientHelper;
+	}
+
+	@Override
+	public SessionId getSessionId(Author introducer, Author alice,
+			Author bob) {
+		boolean isAlice = isAlice(alice.getId(), bob.getId());
+		byte[] hash = crypto.hash(
+				LABEL_SESSION_ID,
+				introducer.getId().getBytes(),
+				isAlice ? alice.getId().getBytes() : bob.getId().getBytes(),
+				isAlice ? bob.getId().getBytes() : alice.getId().getBytes()
+		);
+		return new SessionId(hash);
+	}
+
+	@Override
+	public KeyPair generateKeyPair() {
+		return crypto.generateAgreementKeyPair();
+	}
+
+	@Override
+	public boolean isAlice(AuthorId alice, AuthorId bob) {
+		byte[] a = alice.getBytes();
+		byte[] b = bob.getBytes();
+		return Bytes.COMPARATOR.compare(new Bytes(a), new Bytes(b)) < 0;
+	}
+
+	@Override
+	@SuppressWarnings("ConstantConditions")
+	public SecretKey deriveMasterKey(IntroduceeSession s, boolean alice)
+			throws GeneralSecurityException {
+		return deriveMasterKey(s.getEphemeralPublicKey(),
+				s.getEphemeralPrivateKey(), s.getRemotePublicKey(), alice);
+	}
+
+	SecretKey deriveMasterKey(byte[] publicKey, byte[] privateKey,
+			byte[] remotePublicKey, boolean alice)
+			throws GeneralSecurityException {
+		KeyParser kp = crypto.getAgreementKeyParser();
+		PublicKey remoteEphemeralPublicKey = kp.parsePublicKey(remotePublicKey);
+		PublicKey ephemeralPublicKey = kp.parsePublicKey(publicKey);
+		PrivateKey ephemeralPrivateKey = kp.parsePrivateKey(privateKey);
+		KeyPair keyPair = new KeyPair(ephemeralPublicKey, ephemeralPrivateKey);
+		return crypto.deriveSharedSecret(
+				LABEL_MASTER_KEY,
+				remoteEphemeralPublicKey,
+				keyPair,
+				new byte[] {CLIENT_VERSION},
+				alice ? publicKey : remotePublicKey,
+				alice ? remotePublicKey : publicKey
+		);
+	}
+
+	@Override
+	public SecretKey deriveMacKey(SecretKey masterKey, boolean alice) {
+		return crypto.deriveKey(
+				alice ? LABEL_ALICE_MAC_KEY : LABEL_BOB_MAC_KEY,
+				masterKey
+		);
+	}
+
+	@Override
+	@SuppressWarnings("ConstantConditions")
+	public byte[] mac(SecretKey macKey, IntroduceeSession s,
+			AuthorId localAuthorId, boolean alice) throws FormatException {
+		return mac(macKey, s.getIntroducer().getId(), localAuthorId,
+				s.getRemoteAuthor().getId(), s.getAcceptTimestamp(),
+				s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(),
+				s.getRemotePublicKey(), s.getTransportProperties(),
+				s.getRemoteTransportProperties(), alice);
+	}
+
+	byte[] mac(SecretKey macKey, AuthorId introducerId,
+			AuthorId localAuthorId, AuthorId remoteAuthorId,
+			long acceptTimestamp, long remoteAcceptTimestamp,
+			byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey,
+			Map<TransportId, TransportProperties> transportProperties,
+			Map<TransportId, TransportProperties> remoteTransportProperties,
+			boolean alice) throws FormatException {
+		BdfList localInfo = BdfList.of(
+				localAuthorId,
+				acceptTimestamp,
+				ephemeralPublicKey,
+				clientHelper.toDictionary(transportProperties)
+		);
+		BdfList remoteInfo = BdfList.of(
+				remoteAuthorId,
+				remoteAcceptTimestamp,
+				remoteEphemeralPublicKey,
+				clientHelper.toDictionary(remoteTransportProperties)
+		);
+		BdfList macList = BdfList.of(
+				introducerId,
+				alice ? localInfo : remoteInfo,
+				alice ? remoteInfo : localInfo
+		);
+		return crypto.mac(
+				LABEL_AUTH_MAC,
+				macKey,
+				clientHelper.toByteArray(macList)
+		);
+	}
+
+	@Override
+	@SuppressWarnings("ConstantConditions")
+	public void verifyMac(byte[] mac, IntroduceeSession s,
+			AuthorId localAuthorId)
+			throws GeneralSecurityException, FormatException {
+		boolean alice = isAlice(localAuthorId, s.getRemoteAuthor().getId());
+		verifyMac(mac, new SecretKey(s.getMasterKey()),
+				s.getIntroducer().getId(), localAuthorId,
+				s.getRemoteAuthor().getId(), s.getAcceptTimestamp(),
+				s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(),
+				s.getRemotePublicKey(), s.getTransportProperties(),
+				s.getRemoteTransportProperties(), !alice);
+	}
+
+	void verifyMac(byte[] mac, SecretKey masterKey,
+			AuthorId introducerId, AuthorId localAuthorId,
+			AuthorId remoteAuthorId, long acceptTimestamp,
+			long remoteAcceptTimestamp, byte[] ephemeralPublicKey,
+			byte[] remoteEphemeralPublicKey,
+			Map<TransportId, TransportProperties> transportProperties,
+			Map<TransportId, TransportProperties> remoteTransportProperties,
+			boolean alice) throws GeneralSecurityException, FormatException {
+		SecretKey macKey = deriveMacKey(masterKey, alice);
+		byte[] calculatedMac =
+				mac(macKey, introducerId, localAuthorId, remoteAuthorId,
+						acceptTimestamp, remoteAcceptTimestamp,
+						ephemeralPublicKey, remoteEphemeralPublicKey,
+						transportProperties, remoteTransportProperties, !alice);
+		if (!Arrays.equals(mac, calculatedMac)) {
+			throw new GeneralSecurityException();
+		}
+	}
+
+	@Override
+	public byte[] sign(SecretKey macKey, byte[] privateKey)
+			throws GeneralSecurityException {
+		return crypto.sign(
+				LABEL_AUTH_SIGN,
+				getNonce(macKey),
+				privateKey
+		);
+	}
+
+	@Override
+	@SuppressWarnings("ConstantConditions")
+	public void verifySignature(byte[] signature, IntroduceeSession s,
+			AuthorId localAuthorId) throws GeneralSecurityException {
+		boolean alice = isAlice(s.getRemoteAuthor().getId(), localAuthorId);
+		SecretKey macKey = deriveMacKey(new SecretKey(s.getMasterKey()), alice);
+		verifySignature(macKey, s.getRemoteAuthor().getPublicKey(), signature);
+	}
+
+	void verifySignature(SecretKey macKey, byte[] publicKey,
+			byte[] signature) throws GeneralSecurityException {
+		byte[] nonce = getNonce(macKey);
+		if (!crypto.verify(LABEL_AUTH_SIGN, nonce, publicKey, signature)) {
+			throw new GeneralSecurityException();
+		}
+	}
+
+	private byte[] getNonce(SecretKey macKey) {
+		return crypto.mac(LABEL_AUTH_NONCE, macKey);
+	}
+
+}
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java
new file mode 100644
index 0000000000..57cadce0e2
--- /dev/null
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoImplTest.java
@@ -0,0 +1,142 @@
+package org.briarproject.briar.introduction2;
+
+import org.briarproject.bramble.api.client.ClientHelper;
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.crypto.KeyPair;
+import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorFactory;
+import org.briarproject.bramble.api.identity.LocalAuthor;
+import org.briarproject.bramble.api.plugin.TransportId;
+import org.briarproject.bramble.api.properties.TransportProperties;
+import org.briarproject.bramble.test.BrambleTestCase;
+import org.briarproject.briar.api.client.SessionId;
+import org.briarproject.briar.test.BriarIntegrationTestComponent;
+import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
+import org.junit.Test;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
+import static org.briarproject.bramble.util.StringUtils.fromHexString;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class IntroductionCryptoImplTest extends BrambleTestCase {
+
+	@Inject
+	ClientHelper clientHelper;
+	@Inject
+	AuthorFactory authorFactory;
+	@Inject
+	CryptoComponent cryptoComponent;
+
+	private final IntroductionCryptoImpl crypto;
+
+	private final Author introducer;
+	private final LocalAuthor alice, bob;
+	private final long aliceAcceptTimestamp = 42L;
+	private final long bobAcceptTimestamp = 1337L;
+	private final SecretKey masterKey =
+			new SecretKey(getRandomBytes(SecretKey.LENGTH));
+	private final KeyPair aliceEphemeral, bobEphemeral;
+	private final Map<TransportId, TransportProperties> aliceTransport =
+			getTransportPropertiesMap(3);
+	private final Map<TransportId, TransportProperties> bobTransport =
+			getTransportPropertiesMap(3);
+
+	public IntroductionCryptoImplTest() {
+		BriarIntegrationTestComponent component =
+				DaggerBriarIntegrationTestComponent.builder().build();
+		component.inject(this);
+		crypto = new IntroductionCryptoImpl(cryptoComponent, clientHelper);
+
+		// create actual deterministic authors for testing
+		introducer = authorFactory
+				.createAuthor("Introducer", new byte[] {0x1, 0x2, 0x3});
+		alice = authorFactory.createLocalAuthor("Alice",
+				fromHexString(
+						"A626F080C94771698F86B4B4094C4F560904B53398805AE02BA2343F1829187A"),
+				fromHexString(
+						"60F010187AF91ACA15141E8C811EC8E79C7CAA6461C21A852BB03066C89B0A70"));
+		bob = authorFactory.createLocalAuthor("Bob",
+				fromHexString(
+						"A0D0FED1CE4674D8B6441AD0A664E41BF60D489F35DA11F52AF923540848546F"),
+				fromHexString(
+						"20B25BE7E999F68FE07189449E91984FA79121DBFF28A651669A3CF512D6A758"));
+		aliceEphemeral = crypto.generateKeyPair();
+		bobEphemeral = crypto.generateKeyPair();
+	}
+
+	@Test
+	public void testGetSessionId() {
+		SessionId s1 = crypto.getSessionId(introducer, alice, bob);
+		SessionId s2 = crypto.getSessionId(introducer, bob, alice);
+		assertEquals(s1, s2);
+	}
+
+	@Test
+	public void testIsAlice() {
+		assertTrue(crypto.isAlice(alice.getId(), bob.getId()));
+		assertFalse(crypto.isAlice(bob.getId(), alice.getId()));
+	}
+
+	@Test
+	public void testDeriveMasterKey() throws Exception {
+		SecretKey aliceMasterKey = crypto.deriveMasterKey(alice.getPublicKey(),
+				alice.getPrivateKey(), bob.getPublicKey(), true);
+		SecretKey bobMasterKey = crypto.deriveMasterKey(bob.getPublicKey(),
+				bob.getPrivateKey(), alice.getPublicKey(), false);
+		assertArrayEquals(aliceMasterKey.getBytes(), bobMasterKey.getBytes());
+	}
+
+	@Test
+	public void testAliceMac() throws Exception {
+		SecretKey aliceMacKey = crypto.deriveMacKey(masterKey, true);
+		byte[] aliceMac =
+				crypto.mac(aliceMacKey, introducer.getId(), alice.getId(),
+						bob.getId(), aliceAcceptTimestamp, bobAcceptTimestamp,
+						aliceEphemeral.getPublic().getEncoded(),
+						bobEphemeral.getPublic().getEncoded(), aliceTransport,
+						bobTransport, true);
+
+		crypto.verifyMac(aliceMac, masterKey, introducer.getId(), bob.getId(),
+				alice.getId(), bobAcceptTimestamp, aliceAcceptTimestamp,
+				bobEphemeral.getPublic().getEncoded(),
+				aliceEphemeral.getPublic().getEncoded(), bobTransport,
+				aliceTransport, true);
+	}
+
+	@Test
+	public void testBobMac() throws Exception {
+		SecretKey bobMacKey = crypto.deriveMacKey(masterKey, false);
+		byte[] bobMac =
+				crypto.mac(bobMacKey, introducer.getId(), bob.getId(),
+						alice.getId(), bobAcceptTimestamp, aliceAcceptTimestamp,
+						bobEphemeral.getPublic().getEncoded(),
+						aliceEphemeral.getPublic().getEncoded(), bobTransport,
+						aliceTransport, false);
+
+		crypto.verifyMac(bobMac, masterKey, introducer.getId(), alice.getId(),
+				bob.getId(), aliceAcceptTimestamp, bobAcceptTimestamp,
+				aliceEphemeral.getPublic().getEncoded(),
+				bobEphemeral.getPublic().getEncoded(), aliceTransport,
+				bobTransport, false);
+	}
+
+	@Test
+	public void testSign() throws Exception {
+		KeyPair keyPair = cryptoComponent.generateSignatureKeyPair();
+		SecretKey macKey = crypto.deriveMacKey(masterKey, true);
+		byte[] signature =
+				crypto.sign(macKey, keyPair.getPrivate().getEncoded());
+		crypto.verifySignature(macKey, keyPair.getPublic().getEncoded(),
+				signature);
+	}
+
+}
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java
new file mode 100644
index 0000000000..139c4ca408
--- /dev/null
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction2/IntroductionCryptoTest.java
@@ -0,0 +1,46 @@
+package org.briarproject.briar.introduction2;
+
+import org.briarproject.bramble.api.UniqueId;
+import org.briarproject.bramble.api.client.ClientHelper;
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.test.BrambleMockTestCase;
+import org.briarproject.briar.api.client.SessionId;
+import org.jmock.Expectations;
+import org.junit.Test;
+
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.briar.api.introduction2.IntroductionConstants.LABEL_SESSION_ID;
+import static org.junit.Assert.assertEquals;
+
+public class IntroductionCryptoTest extends BrambleMockTestCase {
+
+	private final CryptoComponent cryptoComponent =
+			context.mock(CryptoComponent.class);
+	private final ClientHelper clientHelper = context.mock(ClientHelper.class);
+
+	private final IntroductionCrypto crypto =
+			new IntroductionCryptoImpl(cryptoComponent, clientHelper);
+
+	private final Author introducer = getAuthor();
+	private final Author alice = getAuthor(), bob = getAuthor();
+	private final byte[] hash = getRandomBytes(UniqueId.LENGTH);
+
+	@Test
+	public void testGetSessionId() {
+		boolean isAlice = crypto.isAlice(alice.getId(), bob.getId());
+		context.checking(new Expectations() {{
+			oneOf(cryptoComponent).hash(
+					LABEL_SESSION_ID,
+					introducer.getId().getBytes(),
+					isAlice ? alice.getId().getBytes() : bob.getId().getBytes(),
+					isAlice ? bob.getId().getBytes() : alice.getId().getBytes()
+			);
+			will(returnValue(hash));
+		}});
+		SessionId sessionId = crypto.getSessionId(introducer, alice, bob);
+		assertEquals(new SessionId(hash), sessionId);
+	}
+
+}
diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
index 54709815e4..fe2e745c0d 100644
--- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
@@ -37,6 +37,7 @@ import org.briarproject.briar.blog.BlogModule;
 import org.briarproject.briar.client.BriarClientModule;
 import org.briarproject.briar.forum.ForumModule;
 import org.briarproject.briar.introduction.IntroductionModule;
+import org.briarproject.briar.introduction2.IntroductionCryptoImplTest;
 import org.briarproject.briar.introduction2.MessageEncoderParserIntegrationTest;
 import org.briarproject.briar.introduction2.SessionEncoderParserIntegrationTest;
 import org.briarproject.briar.messaging.MessagingModule;
@@ -80,6 +81,7 @@ public interface BriarIntegrationTestComponent {
 
 	void inject(MessageEncoderParserIntegrationTest init);
 	void inject(SessionEncoderParserIntegrationTest init);
+	void inject(IntroductionCryptoImplTest init);
 
 	void inject(BlogModule.EagerSingletons init);
 
-- 
GitLab