diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/SecretKey.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/SecretKey.java index 319f9305b..b88bc01da 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/SecretKey.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/SecretKey.java @@ -1,23 +1,19 @@ package org.briarproject.bramble.api.crypto; +import org.briarproject.bramble.api.Bytes; + /** * A secret key used for encryption and/or authentication. */ -public class SecretKey { +public class SecretKey extends Bytes { /** * The length of a secret key in bytes. */ public static final int LENGTH = 32; - private final byte[] key; - public SecretKey(byte[] key) { + super(key); if (key.length != LENGTH) throw new IllegalArgumentException(); - this.key = key; - } - - public byte[] getBytes() { - return key; } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java index dcf8e8ce9..12bc36248 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/lifecycle/LifecycleManager.java @@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Wakeful; import java.util.concurrent.ExecutorService; @@ -30,26 +31,6 @@ public interface LifecycleManager { SUCCESS } - /** - * The minimum reasonable value for the system clock, in milliseconds - * since the Unix epoch. {@link #startServices(SecretKey)} will return - * {@link StartResult#CLOCK_ERROR} if the system clock reports an earlier - * time. - *

- * 1 Jan 2021, 00:00:00 UTC - */ - long MIN_REASONABLE_TIME_MS = 1_609_459_200_000L; - - /** - * The maximum reasonable value for the system clock, in milliseconds - * since the Unix epoch. {@link #startServices(SecretKey)} will return - * {@link StartResult#CLOCK_ERROR} if the system clock reports a later - * time. - *

- * 1 Jan 2121, 00:00:00 UTC - */ - long MAX_REASONABLE_TIME_MS = 4_765_132_800_000L; - /** * The state the lifecycle can be in. * Returned by {@link #getLifecycleState()} @@ -86,6 +67,10 @@ public interface LifecycleManager { /** * Opens the {@link DatabaseComponent} using the given key and starts any * registered {@link Service Services}. + * + * @return {@link StartResult#CLOCK_ERROR} if the system clock is earlier + * than {@link Clock#MIN_REASONABLE_TIME_MS} or later than + * {@link Clock#MAX_REASONABLE_TIME_MS}. */ @Wakeful StartResult startServices(SecretKey dbKey); diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java b/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java index 2b55c4196..1835aa2bc 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java @@ -6,6 +6,22 @@ package org.briarproject.bramble.api.system; */ public interface Clock { + /** + * The minimum reasonable value for the system clock, in milliseconds + * since the Unix epoch. + *

+ * 1 Jan 2021, 00:00:00 UTC + */ + long MIN_REASONABLE_TIME_MS = 1_609_459_200_000L; + + /** + * The maximum reasonable value for the system clock, in milliseconds + * since the Unix epoch. + *

+ * 1 Jan 2121, 00:00:00 UTC + */ + long MAX_REASONABLE_TIME_MS = 4_765_132_800_000L; + /** * @see System#currentTimeMillis() */ diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java index e39e9aa62..3e171904e 100644 --- a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java +++ b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java @@ -163,10 +163,15 @@ public class TestUtils { public static Message getMessage(GroupId groupId) { int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH); - return getMessage(groupId, bodyLength); + return getMessage(groupId, bodyLength, timestamp); } public static Message getMessage(GroupId groupId, int bodyLength) { + return getMessage(groupId, bodyLength, timestamp); + } + + public static Message getMessage(GroupId groupId, int bodyLength, + long timestamp) { MessageId id = new MessageId(getRandomId()); byte[] body = getRandomBytes(bodyLength); return new Message(id, groupId, timestamp, body); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java index 9e3bd03a7..27870ca23 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java @@ -47,6 +47,7 @@ import javax.inject.Inject; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; +import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS; import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.contact.ContactExchangeRecordTypes.CONTACT_INFO; import static org.briarproject.bramble.util.ValidationUtils.checkLength; @@ -184,6 +185,10 @@ class ContactExchangeManagerImpl implements ContactExchangeManager { // The agreed timestamp is the minimum of the peers' timestamps long timestamp = Math.min(localTimestamp, remoteInfo.timestamp); + if (timestamp < MIN_REASONABLE_TIME_MS) { + LOG.warning("Timestamp is too old"); + throw new FormatException(); + } // Add the contact Contact contact = addContact(p, remoteInfo.author, localAuthor, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java index 5efab84ca..992f05bdb 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/lifecycle/LifecycleManagerImpl.java @@ -41,6 +41,8 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; +import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS; +import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS; import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidator.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidator.java index 1f4053b2f..c371fca77 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidator.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidator.java @@ -19,6 +19,7 @@ import javax.annotation.concurrent.Immutable; import static java.util.Collections.singletonList; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; +import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS; import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE; import static org.briarproject.bramble.transport.agreement.MessageType.KEY; import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY; @@ -42,13 +43,14 @@ class TransportKeyAgreementValidator extends BdfMessageValidator { protected BdfMessageContext validateMessage(Message m, Group g, BdfList body) throws FormatException { MessageType type = MessageType.fromValue(body.getLong(0).intValue()); - if (type == KEY) return validateKeyMessage(body); + if (type == KEY) return validateKeyMessage(m.getTimestamp(), body); else if (type == ACTIVATE) return validateActivateMessage(body); else throw new AssertionError(); } - private BdfMessageContext validateKeyMessage(BdfList body) + private BdfMessageContext validateKeyMessage(long timestamp, BdfList body) throws FormatException { + if (timestamp < MIN_REASONABLE_TIME_MS) throw new FormatException(); // Message type, transport ID, public key checkSize(body, 3); String transportId = body.getString(1); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/lifecycle/LifecycleManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/lifecycle/LifecycleManagerImplTest.java index 079a02b5e..d057573fc 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/lifecycle/LifecycleManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/lifecycle/LifecycleManagerImplTest.java @@ -15,10 +15,10 @@ import org.junit.Test; import java.util.concurrent.atomic.AtomicBoolean; import static junit.framework.TestCase.assertTrue; -import static org.briarproject.bramble.api.lifecycle.LifecycleManager.MAX_REASONABLE_TIME_MS; -import static org.briarproject.bramble.api.lifecycle.LifecycleManager.MIN_REASONABLE_TIME_MS; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; +import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS; +import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS; import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.junit.Assert.assertEquals; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidatorTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidatorTest.java index 0437a026c..0a8be77ee 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidatorTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/agreement/TransportKeyAgreementValidatorTest.java @@ -19,6 +19,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; +import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS; import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID; import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION; import static org.briarproject.bramble.test.TestUtils.getGroup; @@ -109,6 +110,27 @@ public class TransportKeyAgreementValidatorTest extends BrambleMockTestCase { assertArrayEquals(publicKey, d.getRaw(MSG_KEY_PUBLIC_KEY)); } + @Test + public void testAcceptsMinTimestampKeyMsg() throws Exception { + Message message = + getMessage(group.getId(), 1234, MIN_REASONABLE_TIME_MS); + TransportId transportId = new TransportId(getRandomString(1)); + context.checking(new Expectations() {{ + oneOf(messageEncoder) + .encodeMessageMetadata(transportId, KEY, false); + will(returnValue(new BdfDictionary())); + }}); + + byte[] publicKey = getRandomBytes(1); + BdfList body = + BdfList.of(KEY.getValue(), transportId.getString(), publicKey); + BdfMessageContext msgCtx = + validator.validateMessage(message, group, body); + assertEquals(emptyList(), msgCtx.getDependencies()); + BdfDictionary d = msgCtx.getDictionary(); + assertArrayEquals(publicKey, d.getRaw(MSG_KEY_PUBLIC_KEY)); + } + @Test(expected = FormatException.class) public void testRejectsTooLongKeyMsg() throws Exception { BdfList body = BdfList.of(KEY.getValue(), getRandomString(1), @@ -168,6 +190,15 @@ public class TransportKeyAgreementValidatorTest extends BrambleMockTestCase { validator.validateMessage(message, group, body); } + @Test(expected = FormatException.class) + public void testRejectsTooOldTimestampKeyMsg() throws Exception { + Message message = + getMessage(group.getId(), 1234, MIN_REASONABLE_TIME_MS - 1); + BdfList body = BdfList.of(KEY.getValue(), getRandomString(1), + getRandomBytes(1)); + validator.validateMessage(message, group, body); + } + @Test public void testAcceptsActivateMsg() throws Exception { TransportId transportId = new TransportId(getRandomString(1)); diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java index 41a3d71af..d198a3bd3 100644 --- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java +++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java @@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.PrivateKey; @@ -48,6 +49,7 @@ import javax.inject.Inject; import static java.lang.Math.max; import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH; import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES; @@ -453,30 +455,30 @@ class IntroduceeProtocolEngine long timestamp = Math.min(s.getLocal().acceptTimestamp, s.getRemote().acceptTimestamp); if (timestamp == -1) throw new AssertionError(); + if (timestamp < MIN_REASONABLE_TIME_MS) { + LOG.warning("Timestamp is too old"); + return abort(txn, s, m.getMessageId()); + } Map keys = null; try { - contactManager.addContact(txn, s.getRemote().author, - localAuthor.getId(), false); + ContactId contactId = contactManager.addContact(txn, + s.getRemote().author, localAuthor.getId(), false); // Only add transport properties and keys when the contact was added // This will be changed once we have a way to reset state for peers // that were contacts already at some point in the past. - Contact c = contactManager.getContact(txn, - s.getRemote().author.getId(), localAuthor.getId()); // add the keys to the new contact - keys = keyManager.addRotationKeys(txn, c.getId(), + keys = keyManager.addRotationKeys(txn, contactId, new SecretKey(s.getMasterKey()), timestamp, s.getLocal().alice, false); // add signed transport properties for the contact - transportPropertyManager.addRemoteProperties(txn, c.getId(), + transportPropertyManager.addRemoteProperties(txn, contactId, s.getRemote().transportProperties); } catch (ContactExistsException e) { - // Ignore this, because the other introducee might have deleted us. - // So we still want updated transport properties - // and new transport keys. + // Ignore this, because the other introducee might have deleted us } // send ACTIVATE message with a MAC diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeProtocolEngineTest.java new file mode 100644 index 000000000..b78817352 --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeProtocolEngineTest.java @@ -0,0 +1,257 @@ +package org.briarproject.briar.introduction; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.ContactManager; +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.BdfDictionary; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.properties.TransportPropertyManager; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.transport.KeyManager; +import org.briarproject.bramble.api.transport.KeySetId; +import org.briarproject.bramble.api.versioning.ClientVersioningManager; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.briarproject.briar.api.autodelete.AutoDeleteManager; +import org.briarproject.briar.api.client.MessageTracker; +import org.briarproject.briar.api.client.SessionId; +import org.briarproject.briar.api.conversation.ConversationManager; +import org.briarproject.briar.api.identity.AuthorManager; +import org.jmock.Expectations; +import org.junit.Test; + +import java.util.Collections; +import java.util.Random; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES; +import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES; +import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS; +import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey; +import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; +import static org.briarproject.bramble.test.TestUtils.getAuthor; +import static org.briarproject.bramble.test.TestUtils.getContactId; +import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; +import static org.briarproject.bramble.test.TestUtils.getMessage; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.bramble.test.TestUtils.getSecretKey; +import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; +import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_ACTIVATE; +import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH; +import static org.briarproject.briar.introduction.IntroduceeState.START; +import static org.briarproject.briar.introduction.MessageType.ABORT; +import static org.briarproject.briar.introduction.MessageType.ACTIVATE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class IntroduceeProtocolEngineTest extends BrambleMockTestCase { + + private final DatabaseComponent db = context.mock(DatabaseComponent.class); + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final ContactManager contactManager = + context.mock(ContactManager.class); + private final ContactGroupFactory contactGroupFactory = + context.mock(ContactGroupFactory.class); + private final MessageTracker messageTracker = + context.mock(MessageTracker.class); + private final IdentityManager identityManager = + context.mock(IdentityManager.class); + private final AuthorManager authorManager = + context.mock(AuthorManager.class); + private final MessageParser messageParser = + context.mock(MessageParser.class); + private final MessageEncoder messageEncoder = + context.mock(MessageEncoder.class); + private final IntroductionCrypto crypto = + context.mock(IntroductionCrypto.class); + private final KeyManager keyManager = context.mock(KeyManager.class); + private final TransportPropertyManager transportPropertyManager = + context.mock(TransportPropertyManager.class); + private final ClientVersioningManager clientVersioningManager = + context.mock(ClientVersioningManager.class); + private final AutoDeleteManager autoDeleteManager = + context.mock(AutoDeleteManager.class); + private final ConversationManager conversationManager = + context.mock(ConversationManager.class); + private final Clock clock = context.mock(Clock.class); + + private final Author introducer = getAuthor(); + private final Author remoteIntroducee = getAuthor(); + private final LocalAuthor localIntroducee = getLocalAuthor(); + private final ContactId contactId = getContactId(); + private final GroupId contactGroupId = new GroupId(getRandomId()); + private final SessionId sessionId = new SessionId(getRandomId()); + private final boolean alice = new Random().nextBoolean(); + private final long now = System.currentTimeMillis(); + private final long requestTimestamp = now - 12345; + private final long localAcceptTimestamp = requestTimestamp + 123; + private final long remoteAuthTimestamp = localAcceptTimestamp + 123; + private final MessageId lastLocalMessageId = new MessageId(getRandomId()); + private final MessageId lastRemoteMessageId = new MessageId(getRandomId()); + private final PublicKey localPublicKey = getAgreementPublicKey(); + private final PrivateKey localPrivateKey = getAgreementPrivateKey(); + private final PublicKey remotePublicKey = getAgreementPublicKey(); + private final SecretKey localMacKey = getSecretKey(); + private final SecretKey remoteMacKey = getSecretKey(); + private final SecretKey masterKey = getSecretKey(); + private final byte[] remoteMac = getRandomBytes(MAC_BYTES); + private final byte[] localMac = getRandomBytes(MAC_BYTES); + private final byte[] remoteSignature = getRandomBytes(MAX_SIGNATURE_BYTES); + + private final IntroduceeProtocolEngine engine = + new IntroduceeProtocolEngine(db, clientHelper, contactManager, + contactGroupFactory, messageTracker, identityManager, + authorManager, messageParser, messageEncoder, crypto, + keyManager, transportPropertyManager, + clientVersioningManager, autoDeleteManager, + conversationManager, clock); + + @Test + public void testDoesNotAbortSessionIfTimestampIsMaxAge() throws Exception { + Transaction txn = new Transaction(null, false); + + long remoteAcceptTimestamp = MIN_REASONABLE_TIME_MS; + IntroduceeSession session = + createAwaitAuthSession(remoteAcceptTimestamp); + + AuthMessage authMessage = new AuthMessage(new MessageId(getRandomId()), + contactGroupId, remoteAuthTimestamp, lastRemoteMessageId, + sessionId, remoteMac, remoteSignature); + + Message activateMessage = getMessage(contactGroupId, 1234, now); + BdfDictionary activateMeta = new BdfDictionary(); + + context.checking(new Expectations() {{ + // Verify the auth message + oneOf(identityManager).getLocalAuthor(txn); + will(returnValue(localIntroducee)); + oneOf(crypto).verifyAuthMac(remoteMac, session, + localIntroducee.getId()); + oneOf(crypto).verifySignature(remoteSignature, session); + // Add the contact + oneOf(contactManager).addContact(txn, remoteIntroducee, + localIntroducee.getId(), false); + will(returnValue(contactId)); + oneOf(keyManager).addRotationKeys(txn, contactId, masterKey, + remoteAcceptTimestamp, alice, false); + will(returnValue(emptyMap())); + oneOf(transportPropertyManager).addRemoteProperties(txn, contactId, + emptyMap()); + // Send the activate message + oneOf(crypto).activateMac(session); + will(returnValue(localMac)); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(messageEncoder).encodeActivateMessage(contactGroupId, now, + lastLocalMessageId, sessionId, localMac); + will(returnValue(activateMessage)); + oneOf(messageEncoder).encodeMetadata(ACTIVATE, sessionId, now, + true, true, false, NO_AUTO_DELETE_TIMER, false); + will(returnValue(activateMeta)); + oneOf(clientHelper).addLocalMessage(txn, activateMessage, + activateMeta, true, false); + }}); + + IntroduceeSession after = + engine.onAuthMessage(txn, session, authMessage); + + assertEquals(AWAIT_ACTIVATE, after.getState()); + assertNull(after.getMasterKey()); + assertEquals(Collections.emptyMap(), + after.getTransportKeys()); + + IntroduceeSession.Local afterLocal = after.getLocal(); + assertEquals(activateMessage.getId(), afterLocal.lastMessageId); + assertEquals(now, afterLocal.lastMessageTimestamp); + + IntroduceeSession.Remote afterRemote = after.getRemote(); + assertEquals(authMessage.getMessageId(), afterRemote.lastMessageId); + } + + @Test + public void testAbortsSessionIfTimestampIsTooOld() throws Exception { + Transaction txn = new Transaction(null, false); + + long remoteAcceptTimestamp = MIN_REASONABLE_TIME_MS - 1; + IntroduceeSession session = + createAwaitAuthSession(remoteAcceptTimestamp); + + AuthMessage authMessage = new AuthMessage(new MessageId(getRandomId()), + contactGroupId, remoteAuthTimestamp, lastRemoteMessageId, + sessionId, remoteMac, remoteSignature); + + BdfDictionary query = new BdfDictionary(); + Message abortMessage = getMessage(contactGroupId, 123, now); + BdfDictionary abortMeta = new BdfDictionary(); + + context.checking(new Expectations() {{ + // Verify the auth message + oneOf(identityManager).getLocalAuthor(txn); + will(returnValue(localIntroducee)); + oneOf(crypto).verifyAuthMac(remoteMac, session, + localIntroducee.getId()); + oneOf(crypto).verifySignature(remoteSignature, session); + // Abort the session + oneOf(messageParser).getRequestsAvailableToAnswerQuery(sessionId); + will(returnValue(query)); + oneOf(clientHelper).getMessageIds(txn, contactGroupId, query); + will(returnValue(emptyList())); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(messageEncoder).encodeAbortMessage(contactGroupId, now, + lastLocalMessageId, sessionId); + will(returnValue(abortMessage)); + oneOf(messageEncoder).encodeMetadata(ABORT, sessionId, now, + true, true, false, NO_AUTO_DELETE_TIMER, false); + will(returnValue(abortMeta)); + oneOf(clientHelper).addLocalMessage(txn, abortMessage, abortMeta, + true, false); + }}); + + IntroduceeSession after = + engine.onAuthMessage(txn, session, authMessage); + + assertEquals(START, after.getState()); + assertNull(after.getMasterKey()); + assertNull(after.getTransportKeys()); + + IntroduceeSession.Local afterLocal = after.getLocal(); + assertEquals(abortMessage.getId(), afterLocal.lastMessageId); + assertEquals(now, afterLocal.lastMessageTimestamp); + assertNull(afterLocal.ephemeralPublicKey); + assertNull(afterLocal.ephemeralPrivateKey); + assertNull(afterLocal.macKey); + + IntroduceeSession.Remote afterRemote = after.getRemote(); + assertEquals(authMessage.getMessageId(), afterRemote.lastMessageId); + assertNull(afterRemote.ephemeralPublicKey); + assertNull(afterRemote.macKey); + } + + private IntroduceeSession createAwaitAuthSession( + long remoteAcceptTimestamp) { + IntroduceeSession.Local local = new IntroduceeSession.Local(alice, + lastLocalMessageId, localAcceptTimestamp, localPublicKey, + localPrivateKey, emptyMap(), localAcceptTimestamp, + localMacKey.getBytes()); + IntroduceeSession.Remote remote = new IntroduceeSession.Remote(!alice, + remoteIntroducee, lastRemoteMessageId, remotePublicKey, + emptyMap(), remoteAcceptTimestamp, remoteMacKey.getBytes()); + return new IntroduceeSession(sessionId, + AWAIT_AUTH, requestTimestamp, contactGroupId, introducer, + local, remote, masterKey.getBytes(), emptyMap()); + } +}