IntroductionCrypto: Create dedicated class to handle introduction related crypto

parent e1fae7ad
......@@ -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";
}
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;
}
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);
}
}
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);
}
}
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);
}
}
......@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment