diff --git a/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemoryAxolotlStore.java b/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemoryAxolotlStore.java new file mode 100644 index 0000000000..4e00186579 --- /dev/null +++ b/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemoryAxolotlStore.java @@ -0,0 +1,115 @@ +package org.whispersystems.test; + +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SessionRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; + +import java.util.List; + +public class InMemoryAxolotlStore implements AxolotlStore { + + private final InMemoryIdentityKeyStore identityKeyStore = new InMemoryIdentityKeyStore(); + private final InMemoryPreKeyStore preKeyStore = new InMemoryPreKeyStore(); + private final InMemorySessionStore sessionStore = new InMemorySessionStore(); + private final InMemorySignedPreKeyStore signedPreKeyStore = new InMemorySignedPreKeyStore(); + + + @Override + public IdentityKeyPair getIdentityKeyPair() { + return identityKeyStore.getIdentityKeyPair(); + } + + @Override + public int getLocalRegistrationId() { + return identityKeyStore.getLocalRegistrationId(); + } + + @Override + public void saveIdentity(long recipientId, IdentityKey identityKey) { + identityKeyStore.saveIdentity(recipientId, identityKey); + } + + @Override + public boolean isTrustedIdentity(long recipientId, IdentityKey identityKey) { + return identityKeyStore.isTrustedIdentity(recipientId, identityKey); + } + + @Override + public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { + return preKeyStore.loadPreKey(preKeyId); + } + + @Override + public void storePreKey(int preKeyId, PreKeyRecord record) { + preKeyStore.storePreKey(preKeyId, record); + } + + @Override + public boolean containsPreKey(int preKeyId) { + return preKeyStore.containsPreKey(preKeyId); + } + + @Override + public void removePreKey(int preKeyId) { + preKeyStore.removePreKey(preKeyId); + } + + @Override + public SessionRecord loadSession(long recipientId, int deviceId) { + return sessionStore.loadSession(recipientId, deviceId); + } + + @Override + public List getSubDeviceSessions(long recipientId) { + return sessionStore.getSubDeviceSessions(recipientId); + } + + @Override + public void storeSession(long recipientId, int deviceId, SessionRecord record) { + sessionStore.storeSession(recipientId, deviceId, record); + } + + @Override + public boolean containsSession(long recipientId, int deviceId) { + return sessionStore.containsSession(recipientId, deviceId); + } + + @Override + public void deleteSession(long recipientId, int deviceId) { + sessionStore.deleteSession(recipientId, deviceId); + } + + @Override + public void deleteAllSessions(long recipientId) { + sessionStore.deleteAllSessions(recipientId); + } + + @Override + public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { + return signedPreKeyStore.loadSignedPreKey(signedPreKeyId); + } + + @Override + public List loadSignedPreKeys() { + return signedPreKeyStore.loadSignedPreKeys(); + } + + @Override + public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { + signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); + } + + @Override + public boolean containsSignedPreKey(int signedPreKeyId) { + return signedPreKeyStore.containsSignedPreKey(signedPreKeyId); + } + + @Override + public void removeSignedPreKey(int signedPreKeyId) { + signedPreKeyStore.removeSignedPreKey(signedPreKeyId); + } +} diff --git a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java index 0feb223e2a..008dae03e3 100644 --- a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java +++ b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java @@ -20,6 +20,7 @@ import org.whispersystems.libaxolotl.protocol.CiphertextMessage; import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage; import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; import org.whispersystems.libaxolotl.protocol.WhisperMessage; +import org.whispersystems.libaxolotl.state.AxolotlStore; import org.whispersystems.libaxolotl.state.IdentityKeyStore; import org.whispersystems.libaxolotl.state.PreKeyBundle; import org.whispersystems.libaxolotl.state.PreKeyRecord; @@ -39,46 +40,35 @@ public class SessionBuilderTest extends AndroidTestCase { public void testBasicPreKeyV2() throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException { - SessionStore aliceSessionStore = new InMemorySessionStore(); - SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); - PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); - IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); - - SessionStore bobSessionStore = new InMemorySessionStore(); - PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + AxolotlStore bobStore = new InMemoryAxolotlStore(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); - PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, 31337, bobPreKeyPair.getPublicKey(), 0, null, null, - bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + bobStore.getIdentityKeyPair().getPublicKey()); aliceSessionBuilder.process(bobPreKey); - assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1)); - assertTrue(!aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh()); - assertTrue(aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2); + assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1)); + assertTrue(aliceStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2); String originalMessage = "L'homme est condamné à être libre"; - SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1); CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize()); - bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); - SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); - assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1)); - assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2); + assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1)); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2); assertTrue(originalMessage.equals(new String(plaintext))); CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); @@ -87,23 +77,18 @@ public class SessionBuilderTest extends AndroidTestCase { byte[] alicePlaintext = aliceSessionCipher.decrypt((WhisperMessage)bobOutgoingMessage); assertTrue(new String(alicePlaintext).equals(originalMessage)); - runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, - bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore); + runInteraction(aliceStore, bobStore); - aliceSessionStore = new InMemorySessionStore(); - aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); - aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); + aliceStore = new InMemoryAxolotlStore(); + aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1); bobPreKeyPair = Curve.generateKeyPair(); - bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), + bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, 31338, bobPreKeyPair.getPublicKey(), - 0, null, null, bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + 0, null, null, bobStore.getIdentityKeyPair().getPublicKey()); - bobPreKeyStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); aliceSessionBuilder.process(bobPreKey); outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); @@ -112,17 +97,17 @@ public class SessionBuilderTest extends AndroidTestCase { bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize())); throw new AssertionError("shouldn't be trusted!"); } catch (UntrustedIdentityException uie) { - bobIdentityKeyStore.saveIdentity(ALICE_RECIPIENT_ID, new PreKeyWhisperMessage(outgoingMessage.serialize()).getIdentityKey()); + bobStore.saveIdentity(ALICE_RECIPIENT_ID, new PreKeyWhisperMessage(outgoingMessage.serialize()).getIdentityKey()); } plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize())); assertTrue(new String(plaintext).equals(originalMessage)); - bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, 31337, Curve.generateKeyPair().getPublicKey(), 0, null, null, - aliceIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + aliceStore.getIdentityKeyPair().getPublicKey()); try { aliceSessionBuilder.process(bobPreKey); @@ -134,54 +119,43 @@ public class SessionBuilderTest extends AndroidTestCase { public void testBasicPreKeyV3() throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException { - SessionStore aliceSessionStore = new InMemorySessionStore(); - SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); - PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); - IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); - SessionStore bobSessionStore = new InMemorySessionStore(); - PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); + ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); + ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), + bobSignedPreKeyPair.getPublicKey().serialize()); - ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); - ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); - byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), - bobSignedPreKeyPair.getPublicKey().serialize()); - - PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, 31337, bobPreKeyPair.getPublicKey(), 22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, - bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + bobStore.getIdentityKeyPair().getPublicKey()); aliceSessionBuilder.process(bobPreKey); - assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1)); - assertTrue(!aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh()); - assertTrue(aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1)); + assertTrue(aliceStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); String originalMessage = "L'homme est condamné à être libre"; - SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1); CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize()); - bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); - bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); + bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); - SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); - assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1)); - assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); - assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null); + assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1)); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null); assertTrue(originalMessage.equals(new String(plaintext))); CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); @@ -190,27 +164,22 @@ public class SessionBuilderTest extends AndroidTestCase { byte[] alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize())); assertTrue(new String(alicePlaintext).equals(originalMessage)); - runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, - bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore); + runInteraction(aliceStore, bobStore); - aliceSessionStore = new InMemorySessionStore(); - aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); - aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); + aliceStore = new InMemoryAxolotlStore(); + aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1); bobPreKeyPair = Curve.generateKeyPair(); bobSignedPreKeyPair = Curve.generateKeyPair(); - bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize()); - bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), + bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize()); + bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, 31338, bobPreKeyPair.getPublicKey(), 23, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, - bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + bobStore.getIdentityKeyPair().getPublicKey()); - bobPreKeyStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); - bobSignedPreKeyStore.storeSignedPreKey(23, new SignedPreKeyRecord(23, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); + bobStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobStore.storeSignedPreKey(23, new SignedPreKeyRecord(23, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); aliceSessionBuilder.process(bobPreKey); outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); @@ -219,16 +188,16 @@ public class SessionBuilderTest extends AndroidTestCase { plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize())); throw new AssertionError("shouldn't be trusted!"); } catch (UntrustedIdentityException uie) { - bobIdentityKeyStore.saveIdentity(ALICE_RECIPIENT_ID, new PreKeyWhisperMessage(outgoingMessage.serialize()).getIdentityKey()); + bobStore.saveIdentity(ALICE_RECIPIENT_ID, new PreKeyWhisperMessage(outgoingMessage.serialize()).getIdentityKey()); } plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize())); assertTrue(new String(plaintext).equals(originalMessage)); - bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, 31337, Curve.generateKeyPair().getPublicKey(), 23, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, - aliceIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + aliceStore.getIdentityKeyPair().getPublicKey()); try { aliceSessionBuilder.process(bobPreKey); @@ -239,14 +208,8 @@ public class SessionBuilderTest extends AndroidTestCase { } public void testBadSignedPreKeySignature() throws InvalidKeyException, UntrustedIdentityException { - SessionStore aliceSessionStore = new InMemorySessionStore(); - SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); - PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); - IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); @@ -284,37 +247,28 @@ public class SessionBuilderTest extends AndroidTestCase { } public void testRepeatBundleMessageV2() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, NoSessionException { - SessionStore aliceSessionStore = new InMemorySessionStore(); - SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); - PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); - IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); - SessionStore bobSessionStore = new InMemorySessionStore(); - PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); - byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize()); - PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, 31337, bobPreKeyPair.getPublicKey(), 0, null, null, - bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + bobStore.getIdentityKeyPair().getPublicKey()); - bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); - bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); + bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); aliceSessionBuilder.process(bobPreKey); String originalMessage = "L'homme est condamné à être libre"; - SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1); CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes()); CiphertextMessage outgoingMessageTwo = aliceSessionCipher.encrypt(originalMessage.getBytes()); @@ -322,7 +276,7 @@ public class SessionBuilderTest extends AndroidTestCase { PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessageOne.serialize()); - SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); assertTrue(originalMessage.equals(new String(plaintext))); @@ -346,45 +300,37 @@ public class SessionBuilderTest extends AndroidTestCase { } public void testRepeatBundleMessageV3() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, NoSessionException { - SessionStore aliceSessionStore = new InMemorySessionStore(); - SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); - PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); - IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); - SessionStore bobSessionStore = new InMemorySessionStore(); - PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); - byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize()); - PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, 31337, bobPreKeyPair.getPublicKey(), 22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, - bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + bobStore.getIdentityKeyPair().getPublicKey()); - bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); - bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); + bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); aliceSessionBuilder.process(bobPreKey); String originalMessage = "L'homme est condamné à être libre"; - SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1); CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes()); CiphertextMessage outgoingMessageTwo = aliceSessionCipher.encrypt(originalMessage.getBytes()); assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE); + assertTrue(outgoingMessageTwo.getType() == CiphertextMessage.PREKEY_TYPE); PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessageOne.serialize()); - SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); assertTrue(originalMessage.equals(new String(plaintext))); @@ -408,37 +354,28 @@ public class SessionBuilderTest extends AndroidTestCase { } public void testBadMessageBundle() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException { - SessionStore aliceSessionStore = new InMemorySessionStore(); - SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); - PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); - IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); - SessionStore bobSessionStore = new InMemorySessionStore(); - PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); - byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize()); - PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, 31337, bobPreKeyPair.getPublicKey(), 22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, - bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + bobStore.getIdentityKeyPair().getPublicKey()); - bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); - bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); + bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); aliceSessionBuilder.process(bobPreKey); String originalMessage = "L'homme est condamné à être libre"; - SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1); CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes()); assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE); @@ -450,7 +387,7 @@ public class SessionBuilderTest extends AndroidTestCase { badMessage[badMessage.length-10] ^= 0x01; PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(badMessage); - SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); byte[] plaintext = new byte[0]; @@ -461,32 +398,20 @@ public class SessionBuilderTest extends AndroidTestCase { // good. } - assertTrue(bobPreKeyStore.containsPreKey(31337)); + assertTrue(bobStore.containsPreKey(31337)); plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(goodMessage)); assertTrue(originalMessage.equals(new String(plaintext))); - assertTrue(!bobPreKeyStore.containsPreKey(31337)); + assertTrue(!bobStore.containsPreKey(31337)); } public void testBasicKeyExchange() throws InvalidKeyException, LegacyMessageException, InvalidMessageException, DuplicateMessageException, UntrustedIdentityException, StaleKeyExchangeException, InvalidVersionException, NoSessionException { - SessionStore aliceSessionStore = new InMemorySessionStore(); - PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); - SessionStore bobSessionStore = new InMemorySessionStore(); - PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder bobSessionBuilder = new SessionBuilder(bobSessionStore, bobPreKeyStore, - bobSignedPreKeyStore, - bobIdentityKeyStore, - ALICE_RECIPIENT_ID, 1); + AxolotlStore bobStore = new InMemoryAxolotlStore(); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1); KeyExchangeMessage aliceKeyExchangeMessage = aliceSessionBuilder.process(); assertTrue(aliceKeyExchangeMessage != null); @@ -500,52 +425,35 @@ public class SessionBuilderTest extends AndroidTestCase { KeyExchangeMessage response = aliceSessionBuilder.process(new KeyExchangeMessage(bobKeyExchangeMessageBytes)); assertTrue(response == null); - assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1)); - assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1)); + assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1)); + assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1)); - runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, - bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore); + runInteraction(aliceStore, bobStore); - aliceSessionStore = new InMemorySessionStore(); - aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); + aliceStore = new InMemoryAxolotlStore(); + aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); aliceKeyExchangeMessage = aliceSessionBuilder.process(); try { bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage); throw new AssertionError("This identity shouldn't be trusted!"); } catch (UntrustedIdentityException uie) { - bobIdentityKeyStore.saveIdentity(ALICE_RECIPIENT_ID, aliceKeyExchangeMessage.getIdentityKey()); + bobStore.saveIdentity(ALICE_RECIPIENT_ID, aliceKeyExchangeMessage.getIdentityKey()); bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage); } assertTrue(aliceSessionBuilder.process(bobKeyExchangeMessage) == null); - runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, - bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore); + runInteraction(aliceStore, bobStore); } public void testSimultaneousKeyExchange() throws InvalidKeyException, DuplicateMessageException, LegacyMessageException, InvalidMessageException, UntrustedIdentityException, StaleKeyExchangeException, NoSessionException { - SessionStore aliceSessionStore = new InMemorySessionStore(); - PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); - SessionStore bobSessionStore = new InMemorySessionStore(); - PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder bobSessionBuilder = new SessionBuilder(bobSessionStore, bobPreKeyStore, - bobSignedPreKeyStore, - bobIdentityKeyStore, - ALICE_RECIPIENT_ID, 1); + AxolotlStore bobStore = new InMemoryAxolotlStore(); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1); KeyExchangeMessage aliceKeyExchange = aliceSessionBuilder.process(); KeyExchangeMessage bobKeyExchange = bobSessionBuilder.process(); @@ -565,44 +473,33 @@ public class SessionBuilderTest extends AndroidTestCase { assertTrue(aliceAck == null); assertTrue(bobAck == null); - runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, - bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore); + runInteraction(aliceStore, bobStore); } public void testOptionalOneTimePreKey() throws Exception { - SessionStore aliceSessionStore = new InMemorySessionStore(); - SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); - PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); - IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, - aliceSignedPreKeyStore, - aliceIdentityKeyStore, - BOB_RECIPIENT_ID, 1); + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); - SessionStore bobSessionStore = new InMemorySessionStore(); - PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); - byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize()); - PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, 0, null, 22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, - bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + bobStore.getIdentityKeyPair().getPublicKey()); aliceSessionBuilder.process(bobPreKey); - assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1)); - assertTrue(!aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh()); - assertTrue(aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1)); + assertTrue(aliceStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); String originalMessage = "L'homme est condamné à être libre"; - SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1); CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); @@ -610,31 +507,24 @@ public class SessionBuilderTest extends AndroidTestCase { PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize()); assertTrue(!incomingMessage.getPreKeyId().isPresent()); - bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); - bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); + bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); - SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); - assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1)); - assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); - assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null); + assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1)); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null); assertTrue(originalMessage.equals(new String(plaintext))); } - private void runInteraction(SessionStore aliceSessionStore, - PreKeyStore alicePreKeyStore, - SignedPreKeyStore aliceSignedPreKeyStore, - IdentityKeyStore aliceIdentityKeyStore, - SessionStore bobSessionStore, - PreKeyStore bobPreKeyStore, - SignedPreKeyStore bobSignedPreKeyStore, - IdentityKeyStore bobIdentityKeyStore) + private void runInteraction(AxolotlStore aliceStore, AxolotlStore bobStore) throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, NoSessionException { - SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); - SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1); + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); String originalMessage = "smert ze smert"; CiphertextMessage aliceMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); diff --git a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionCipherTest.java b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionCipherTest.java index 192a7a5506..a2a777628a 100644 --- a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionCipherTest.java +++ b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionCipherTest.java @@ -18,6 +18,7 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage; import org.whispersystems.libaxolotl.ratchet.AliceAxolotlParameters; import org.whispersystems.libaxolotl.ratchet.BobAxolotlParameters; import org.whispersystems.libaxolotl.ratchet.RatchetingSession; +import org.whispersystems.libaxolotl.state.AxolotlStore; import org.whispersystems.libaxolotl.state.IdentityKeyStore; import org.whispersystems.libaxolotl.state.PreKeyStore; import org.whispersystems.libaxolotl.state.SessionRecord; @@ -60,20 +61,14 @@ public class SessionCipherTest extends AndroidTestCase { private void runInteraction(SessionRecord aliceSessionRecord, SessionRecord bobSessionRecord) throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, NoSuchAlgorithmException, NoSessionException { - SessionStore aliceSessionStore = new InMemorySessionStore(); - PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); - SessionStore bobSessionStore = new InMemorySessionStore(); - PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); - SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore(); - IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); - aliceSessionStore.storeSession(2L, 1, aliceSessionRecord); - bobSessionStore.storeSession(3L, 1, bobSessionRecord); + aliceStore.storeSession(2L, 1, aliceSessionRecord); + bobStore.storeSession(3L, 1, bobSessionRecord); - SessionCipher aliceCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, 2L, 1); - SessionCipher bobCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, 3L, 1); + SessionCipher aliceCipher = new SessionCipher(aliceStore, 2L, 1); + SessionCipher bobCipher = new SessionCipher(bobStore, 3L, 1); byte[] alicePlaintext = "This is a plaintext message.".getBytes(); CiphertextMessage message = aliceCipher.encrypt(alicePlaintext); diff --git a/libaxolotl/src/androidTest/java/org/whispersystems/test/SimultaneousInitiateTests.java b/libaxolotl/src/androidTest/java/org/whispersystems/test/SimultaneousInitiateTests.java new file mode 100644 index 0000000000..dffde57c6e --- /dev/null +++ b/libaxolotl/src/androidTest/java/org/whispersystems/test/SimultaneousInitiateTests.java @@ -0,0 +1,501 @@ +package org.whispersystems.test; + +import android.test.AndroidTestCase; +import android.util.Log; + +import org.whispersystems.libaxolotl.DuplicateMessageException; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.libaxolotl.InvalidVersionException; +import org.whispersystems.libaxolotl.LegacyMessageException; +import org.whispersystems.libaxolotl.NoSessionException; +import org.whispersystems.libaxolotl.SessionBuilder; +import org.whispersystems.libaxolotl.SessionCipher; +import org.whispersystems.libaxolotl.UntrustedIdentityException; +import org.whispersystems.libaxolotl.ecc.Curve; +import org.whispersystems.libaxolotl.ecc.ECKeyPair; +import org.whispersystems.libaxolotl.protocol.CiphertextMessage; +import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; +import org.whispersystems.libaxolotl.protocol.WhisperMessage; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.state.PreKeyBundle; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.libaxolotl.util.Medium; + +import java.util.Arrays; +import java.util.Random; + +public class SimultaneousInitiateTests extends AndroidTestCase { + + private static final long BOB_RECIPENT_ID = 12345; + private static final long ALICE_RECIPIENT_ID = 6789; + + private static final ECKeyPair aliceSignedPreKey = Curve.generateKeyPair(); + private static final ECKeyPair bobSignedPreKey = Curve.generateKeyPair(); + + private static final int aliceSignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE); + private static final int bobSignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE); + + public void testBasicSimultaneousInitiate() + throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, + InvalidMessageException, DuplicateMessageException, LegacyMessageException, + InvalidKeyIdException, NoSessionException + { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); + + PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); + PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); + + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPENT_ID, 1); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1); + + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + + aliceSessionBuilder.process(bobPreKeyBundle); + bobSessionBuilder.process(alicePreKeyBundle); + + CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); + + assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); + assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize())); + byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); + + assertTrue(new String(alicePlaintext).equals("sample message")); + assertTrue(new String(bobPlaintext).equals("hey there")); + + assertTrue(aliceStore.loadSession(BOB_RECIPENT_ID, 1).getSessionState().getSessionVersion() == 3); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); + + assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] responsePlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize())); + + assertTrue(new String(responsePlaintext).equals("second message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + + CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); + + assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); + + assertTrue(new String(finalPlaintext).equals("third message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + } + + public void testLostSimultaneousInitiate() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException, NoSessionException { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); + + PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); + PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); + + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPENT_ID, 1); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1); + + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + + aliceSessionBuilder.process(bobPreKeyBundle); + bobSessionBuilder.process(alicePreKeyBundle); + + CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); + + assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); + assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); + + assertTrue(new String(bobPlaintext).equals("hey there")); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + + CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); + + assertTrue(aliceResponse.getType() == CiphertextMessage.PREKEY_TYPE); + + byte[] responsePlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(aliceResponse.serialize())); + + assertTrue(new String(responsePlaintext).equals("second message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + + CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); + + assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); + + assertTrue(new String(finalPlaintext).equals("third message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + } + + public void testSimultaneousInitiateLostMessage() + throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, + InvalidMessageException, DuplicateMessageException, LegacyMessageException, + InvalidKeyIdException, NoSessionException + { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); + + PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); + PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); + + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPENT_ID, 1); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1); + + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + + aliceSessionBuilder.process(bobPreKeyBundle); + bobSessionBuilder.process(alicePreKeyBundle); + + CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); + + assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); + assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize())); + byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); + + assertTrue(new String(alicePlaintext).equals("sample message")); + assertTrue(new String(bobPlaintext).equals("hey there")); + + assertTrue(aliceStore.loadSession(BOB_RECIPENT_ID, 1).getSessionState().getSessionVersion() == 3); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); + + assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE); + +// byte[] responsePlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize())); +// +// assertTrue(new String(responsePlaintext).equals("second message")); +// assertTrue(isSessionIdEqual(aliceStore, bobStore)); + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); + + assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); + + assertTrue(new String(finalPlaintext).equals("third message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + } + + public void testSimultaneousInitiateRepeatedMessages() + throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, + InvalidMessageException, DuplicateMessageException, LegacyMessageException, + InvalidKeyIdException, NoSessionException + { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); + + PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); + PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); + + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPENT_ID, 1); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1); + + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + + aliceSessionBuilder.process(bobPreKeyBundle); + bobSessionBuilder.process(alicePreKeyBundle); + + CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); + + assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); + assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize())); + byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); + + assertTrue(new String(alicePlaintext).equals("sample message")); + assertTrue(new String(bobPlaintext).equals("hey there")); + + assertTrue(aliceStore.loadSession(BOB_RECIPENT_ID, 1).getSessionState().getSessionVersion() == 3); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + for (int i=0;i<50;i++) { + Log.w("SimultaneousInitiateTests", "Iteration: " + i); + CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAliceRepeat = bobSessionCipher.encrypt("sample message".getBytes()); + + assertTrue(messageForBobRepeat.getType() == CiphertextMessage.WHISPER_TYPE); + assertTrue(messageForAliceRepeat.getType() == CiphertextMessage.WHISPER_TYPE); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + byte[] alicePlaintextRepeat = aliceSessionCipher.decrypt(new WhisperMessage(messageForAliceRepeat.serialize())); + byte[] bobPlaintextRepeat = bobSessionCipher.decrypt(new WhisperMessage(messageForBobRepeat.serialize())); + + assertTrue(new String(alicePlaintextRepeat).equals("sample message")); + assertTrue(new String(bobPlaintextRepeat).equals("hey there")); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + } + + CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); + + assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] responsePlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize())); + + assertTrue(new String(responsePlaintext).equals("second message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + + CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); + + assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); + + assertTrue(new String(finalPlaintext).equals("third message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + } + + public void testRepeatedSimultaneousInitiateRepeatedMessages() + throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, + InvalidMessageException, DuplicateMessageException, LegacyMessageException, + InvalidKeyIdException, NoSessionException + { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); + + + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPENT_ID, 1); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1); + + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + + for (int i=0;i<15;i++) { + PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); + PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); + + aliceSessionBuilder.process(bobPreKeyBundle); + bobSessionBuilder.process(alicePreKeyBundle); + + CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); + + assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); + assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize())); + byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); + + assertTrue(new String(alicePlaintext).equals("sample message")); + assertTrue(new String(bobPlaintext).equals("hey there")); + + assertTrue(aliceStore.loadSession(BOB_RECIPENT_ID, 1).getSessionState().getSessionVersion() == 3); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + } + + for (int i=0;i<50;i++) { + Log.w("SimultaneousInitiateTests", "Iteration: " + i); + CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAliceRepeat = bobSessionCipher.encrypt("sample message".getBytes()); + + assertTrue(messageForBobRepeat.getType() == CiphertextMessage.WHISPER_TYPE); + assertTrue(messageForAliceRepeat.getType() == CiphertextMessage.WHISPER_TYPE); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + byte[] alicePlaintextRepeat = aliceSessionCipher.decrypt(new WhisperMessage(messageForAliceRepeat.serialize())); + byte[] bobPlaintextRepeat = bobSessionCipher.decrypt(new WhisperMessage(messageForBobRepeat.serialize())); + + assertTrue(new String(alicePlaintextRepeat).equals("sample message")); + assertTrue(new String(bobPlaintextRepeat).equals("hey there")); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + } + + CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); + + assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] responsePlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize())); + + assertTrue(new String(responsePlaintext).equals("second message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + + CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); + + assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); + + assertTrue(new String(finalPlaintext).equals("third message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + } + + public void testRepeatedSimultaneousInitiateLostMessageRepeatedMessages() + throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, + InvalidMessageException, DuplicateMessageException, LegacyMessageException, + InvalidKeyIdException, NoSessionException + { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + AxolotlStore bobStore = new InMemoryAxolotlStore(); + + + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPENT_ID, 1); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1); + + SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPENT_ID, 1); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + +// PreKeyBundle aliceLostPreKeyBundle = createAlicePreKeyBundle(aliceStore); + PreKeyBundle bobLostPreKeyBundle = createBobPreKeyBundle(bobStore); + + aliceSessionBuilder.process(bobLostPreKeyBundle); +// bobSessionBuilder.process(aliceLostPreKeyBundle); + + CiphertextMessage lostMessageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); +// CiphertextMessage lostMessageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); + + for (int i=0;i<15;i++) { + PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); + PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); + + aliceSessionBuilder.process(bobPreKeyBundle); + bobSessionBuilder.process(alicePreKeyBundle); + + CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); + + assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); + assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize())); + byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); + + assertTrue(new String(alicePlaintext).equals("sample message")); + assertTrue(new String(bobPlaintext).equals("hey there")); + + assertTrue(aliceStore.loadSession(BOB_RECIPENT_ID, 1).getSessionState().getSessionVersion() == 3); + assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + } + + for (int i=0;i<50;i++) { + Log.w("SimultaneousInitiateTests", "Iteration: " + i); + CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); + CiphertextMessage messageForAliceRepeat = bobSessionCipher.encrypt("sample message".getBytes()); + + assertTrue(messageForBobRepeat.getType() == CiphertextMessage.WHISPER_TYPE); + assertTrue(messageForAliceRepeat.getType() == CiphertextMessage.WHISPER_TYPE); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + byte[] alicePlaintextRepeat = aliceSessionCipher.decrypt(new WhisperMessage(messageForAliceRepeat.serialize())); + byte[] bobPlaintextRepeat = bobSessionCipher.decrypt(new WhisperMessage(messageForBobRepeat.serialize())); + + assertTrue(new String(alicePlaintextRepeat).equals("sample message")); + assertTrue(new String(bobPlaintextRepeat).equals("hey there")); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + } + + CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); + + assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] responsePlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize())); + + assertTrue(new String(responsePlaintext).equals("second message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + + CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); + + assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); + + assertTrue(new String(finalPlaintext).equals("third message")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + + byte[] lostMessagePlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(lostMessageForBob.serialize())); + assertTrue(new String(lostMessagePlaintext).equals("hey there")); + + assertFalse(isSessionIdEqual(aliceStore, bobStore)); + + CiphertextMessage blastFromThePast = bobSessionCipher.encrypt("unexpected!".getBytes()); + byte[] blastFromThePastPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(blastFromThePast.serialize())); + + assertTrue(new String(blastFromThePastPlaintext).equals("unexpected!")); + assertTrue(isSessionIdEqual(aliceStore, bobStore)); + } + + private boolean isSessionIdEqual(AxolotlStore aliceStore, AxolotlStore bobStore) { + return Arrays.equals(aliceStore.loadSession(BOB_RECIPENT_ID, 1).getSessionState().getAliceBaseKey(), + bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey()); + } + + private PreKeyBundle createAlicePreKeyBundle(AxolotlStore aliceStore) throws InvalidKeyException { + ECKeyPair aliceUnsignedPreKey = Curve.generateKeyPair(); + int aliceUnsignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE); + byte[] aliceSignature = Curve.calculateSignature(aliceStore.getIdentityKeyPair().getPrivateKey(), + aliceSignedPreKey.getPublicKey().serialize()); + + PreKeyBundle alicePreKeyBundle = new PreKeyBundle(1, 1, + aliceUnsignedPreKeyId, aliceUnsignedPreKey.getPublicKey(), + aliceSignedPreKeyId, aliceSignedPreKey.getPublicKey(), + aliceSignature, aliceStore.getIdentityKeyPair().getPublicKey()); + + aliceStore.storeSignedPreKey(aliceSignedPreKeyId, new SignedPreKeyRecord(aliceSignedPreKeyId, System.currentTimeMillis(), aliceSignedPreKey, aliceSignature)); + aliceStore.storePreKey(aliceUnsignedPreKeyId, new PreKeyRecord(aliceUnsignedPreKeyId, aliceUnsignedPreKey)); + + return alicePreKeyBundle; + } + + private PreKeyBundle createBobPreKeyBundle(AxolotlStore bobStore) throws InvalidKeyException { + ECKeyPair bobUnsignedPreKey = Curve.generateKeyPair(); + int bobUnsignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE); + byte[] bobSignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), + bobSignedPreKey.getPublicKey().serialize()); + + PreKeyBundle bobPreKeyBundle = new PreKeyBundle(1, 1, + bobUnsignedPreKeyId, bobUnsignedPreKey.getPublicKey(), + bobSignedPreKeyId, bobSignedPreKey.getPublicKey(), + bobSignature, bobStore.getIdentityKeyPair().getPublicKey()); + + bobStore.storeSignedPreKey(bobSignedPreKeyId, new SignedPreKeyRecord(bobSignedPreKeyId, System.currentTimeMillis(), bobSignedPreKey, bobSignature)); + bobStore.storePreKey(bobUnsignedPreKeyId, new PreKeyRecord(bobUnsignedPreKeyId, bobUnsignedPreKey)); + + return bobPreKeyBundle; + } +} diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java index c5890760c3..2d1c896921 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java @@ -12,6 +12,7 @@ import org.whispersystems.libaxolotl.ratchet.AliceAxolotlParameters; import org.whispersystems.libaxolotl.ratchet.BobAxolotlParameters; import org.whispersystems.libaxolotl.ratchet.RatchetingSession; import org.whispersystems.libaxolotl.ratchet.SymmetricAxolotlParameters; +import org.whispersystems.libaxolotl.state.AxolotlStore; import org.whispersystems.libaxolotl.state.IdentityKeyStore; import org.whispersystems.libaxolotl.state.PreKeyBundle; import org.whispersystems.libaxolotl.state.PreKeyStore; @@ -74,6 +75,16 @@ public class SessionBuilder { this.deviceId = deviceId; } + /** + * Constructs a SessionBuilder + * @param store The {@link org.whispersystems.libaxolotl.state.AxolotlStore} to store all state information in. + * @param recipientId The recipient ID of the remote user to build a session with. + * @param deviceId The device ID of the remote user's physical device. + */ + public SessionBuilder(AxolotlStore store, long recipientId, int deviceId) { + this(store, store, store, store, recipientId, deviceId); + } + /** * Build a new session from a received {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}. * @@ -119,8 +130,7 @@ public class SessionBuilder { return Optional.absent(); } - boolean simultaneousInitiate = sessionRecord.getSessionState().hasUnacknowledgedPreKeyMessage(); - ECKeyPair ourSignedPreKey = signedPreKeyStore.loadSignedPreKey(message.getSignedPreKeyId()).getKeyPair(); + ECKeyPair ourSignedPreKey = signedPreKeyStore.loadSignedPreKey(message.getSignedPreKeyId()).getKeyPair(); BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder(); @@ -136,8 +146,7 @@ public class SessionBuilder { parameters.setOurOneTimePreKey(Optional.absent()); } - if (!simultaneousInitiate) sessionRecord.reset(); - else sessionRecord.archiveCurrentState(); + if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState(); RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create()); @@ -145,8 +154,6 @@ public class SessionBuilder { sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId()); sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize()); - if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true); - if (message.getPreKeyId().isPresent() && message.getPreKeyId().get() != Medium.MAX_VALUE) { return message.getPreKeyId(); } else { @@ -168,8 +175,7 @@ public class SessionBuilder { return Optional.absent(); } - ECKeyPair ourPreKey = preKeyStore.loadPreKey(message.getPreKeyId().get()).getKeyPair(); - boolean simultaneousInitiate = sessionRecord.getSessionState().hasUnacknowledgedPreKeyMessage(); + ECKeyPair ourPreKey = preKeyStore.loadPreKey(message.getPreKeyId().get()).getKeyPair(); BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder(); @@ -180,15 +186,13 @@ public class SessionBuilder { .setTheirIdentityKey(message.getIdentityKey()) .setTheirBaseKey(message.getBaseKey()); - if (!simultaneousInitiate) sessionRecord.reset(); - else sessionRecord.archiveCurrentState(); + if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState(); RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create()); sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId()); - - if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true); + sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize()); if (message.getPreKeyId().get() != Medium.MAX_VALUE) { return message.getPreKeyId(); @@ -227,7 +231,6 @@ public class SessionBuilder { } boolean supportsV3 = preKey.getSignedPreKey() != null; - boolean isExistingSession = sessionStore.containsSession(recipientId, deviceId); SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId); ECKeyPair ourBaseKey = Curve.generateKeyPair(); ECPublicKey theirSignedPreKey = supportsV3 ? preKey.getSignedPreKey() : preKey.getPreKey(); @@ -244,8 +247,7 @@ public class SessionBuilder { .setTheirRatchetKey(theirSignedPreKey) .setTheirOneTimePreKey(supportsV3 ? theirOneTimePreKey : Optional.absent()); - if (isExistingSession) sessionRecord.archiveCurrentState(); - else sessionRecord.reset(); + if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState(); RatchetingSession.initializeSession(sessionRecord.getSessionState(), supportsV3 ? 3 : 2, @@ -254,6 +256,7 @@ public class SessionBuilder { sessionRecord.getSessionState().setUnacknowledgedPreKeyMessage(theirOneTimePreKeyId, preKey.getSignedPreKeyId(), ourBaseKey.getPublicKey()); sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(preKey.getRegistrationId()); + sessionRecord.getSessionState().setAliceBaseKey(ourBaseKey.getPublicKey().serialize()); sessionStore.storeSession(recipientId, deviceId, sessionRecord); identityKeyStore.saveIdentity(recipientId, preKey.getIdentityKey()); @@ -316,7 +319,7 @@ public class SessionBuilder { SymmetricAxolotlParameters parameters = builder.create(); - sessionRecord.reset(); + if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState(); RatchetingSession.initializeSession(sessionRecord.getSessionState(), Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION), @@ -358,7 +361,7 @@ public class SessionBuilder { .setTheirRatchetKey(message.getRatchetKey()) .setTheirIdentityKey(message.getIdentityKey()); - sessionRecord.reset(); + if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState(); RatchetingSession.initializeSession(sessionRecord.getSessionState(), Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION), diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java index 424f3bd0b0..6984966854 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java @@ -25,6 +25,7 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage; import org.whispersystems.libaxolotl.ratchet.ChainKey; import org.whispersystems.libaxolotl.ratchet.MessageKeys; import org.whispersystems.libaxolotl.ratchet.RootKey; +import org.whispersystems.libaxolotl.state.AxolotlStore; import org.whispersystems.libaxolotl.state.IdentityKeyStore; import org.whispersystems.libaxolotl.state.PreKeyStore; import org.whispersystems.libaxolotl.state.SessionRecord; @@ -37,6 +38,7 @@ import org.whispersystems.libaxolotl.util.guava.Optional; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -89,6 +91,10 @@ public class SessionCipher { identityKeyStore, recipientId, deviceId); } + public SessionCipher(AxolotlStore store, long recipientId, int deviceId) { + this(store, store, store, store, recipientId, deviceId); + } + /** * Encrypt a message. * @@ -198,19 +204,28 @@ public class SessionCipher { throws DuplicateMessageException, LegacyMessageException, InvalidMessageException { synchronized (SESSION_LOCK) { - SessionState sessionState = sessionRecord.getSessionState(); - List previousStates = sessionRecord.getPreviousSessionStates(); - List exceptions = new LinkedList<>(); + Iterator previousStates = sessionRecord.getPreviousSessionStates().iterator(); + List exceptions = new LinkedList<>(); try { - return decrypt(sessionState, ciphertext); + SessionState sessionState = new SessionState(sessionRecord.getSessionState()); + byte[] plaintext = decrypt(sessionState, ciphertext); + + sessionRecord.setState(sessionState); + return plaintext; } catch (InvalidMessageException e) { exceptions.add(e); } - for (SessionState previousState : previousStates) { + while (previousStates.hasNext()) { try { - return decrypt(previousState, ciphertext); + SessionState promotedState = new SessionState(previousStates.next()); + byte[] plaintext = decrypt(promotedState, ciphertext); + + previousStates.remove(); + sessionRecord.promoteState(promotedState); + + return plaintext; } catch (InvalidMessageException e) { exceptions.add(e); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/AxolotlStore.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/AxolotlStore.java new file mode 100644 index 0000000000..2fda9a215d --- /dev/null +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/AxolotlStore.java @@ -0,0 +1,6 @@ +package org.whispersystems.libaxolotl.state; + +public interface AxolotlStore + extends IdentityKeyStore, PreKeyStore, SessionStore, SignedPreKeyStore +{ +} diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionRecord.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionRecord.java index 0ddd2a74b3..76c64922cd 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionRecord.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionRecord.java @@ -15,18 +15,25 @@ import static org.whispersystems.libaxolotl.state.StorageProtos.SessionStructure */ public class SessionRecord { - private SessionState sessionState = new SessionState(); - private List previousStates = new LinkedList<>(); + private static final int ARCHIVED_STATES_MAX_LENGTH = 40; - public SessionRecord() {} + private SessionState sessionState = new SessionState(); + private LinkedList previousStates = new LinkedList<>(); + private boolean fresh = false; + + public SessionRecord() { + this.fresh = true; + } public SessionRecord(SessionState sessionState) { this.sessionState = sessionState; + this.fresh = false; } public SessionRecord(byte[] serialized) throws IOException { RecordStructure record = RecordStructure.parseFrom(serialized); this.sessionState = new SessionState(record.getCurrentSession()); + this.fresh = false; for (SessionStructure previousStructure : record.getPreviousSessionsList()) { previousStates.add(new SessionState(previousStructure)); @@ -62,14 +69,9 @@ public class SessionRecord { return previousStates; } - /** - * Reset the current SessionRecord, clearing all "previous" session states, - * and resetting the current {@link org.whispersystems.libaxolotl.state.SessionState} - * to a fresh state. - */ - public void reset() { - this.sessionState = new SessionState(); - this.previousStates = new LinkedList<>(); + + public boolean isFresh() { + return fresh; } /** @@ -78,8 +80,20 @@ public class SessionRecord { * with a fresh reset instance. */ public void archiveCurrentState() { - this.previousStates.add(sessionState); - this.sessionState = new SessionState(); + promoteState(new SessionState()); + } + + public void promoteState(SessionState promotedState) { + this.previousStates.addFirst(sessionState); + this.sessionState = promotedState; + + if (previousStates.size() > ARCHIVED_STATES_MAX_LENGTH) { + previousStates.removeLast(); + } + } + + public void setState(SessionState sessionState) { + this.sessionState = sessionState; } /** diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java index a700e89aa2..9b2b1e2aed 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java @@ -59,6 +59,10 @@ public class SessionState { this.sessionStructure = sessionStructure; } + public SessionState(SessionState copy) { + this.sessionStructure = copy.sessionStructure.toBuilder().build(); + } + public SessionStructure getStructure() { return sessionStructure; } @@ -73,16 +77,6 @@ public class SessionState { .build(); } - public void setNeedsRefresh(boolean needsRefresh) { - this.sessionStructure = this.sessionStructure.toBuilder() - .setNeedsRefresh(needsRefresh) - .build(); - } - - public boolean getNeedsRefresh() { - return this.sessionStructure.getNeedsRefresh(); - } - public void setSessionVersion(int version) { this.sessionStructure = this.sessionStructure.toBuilder() .setSessionVersion(version) diff --git a/library/src/org/whispersystems/textsecure/storage/SessionUtil.java b/library/src/org/whispersystems/textsecure/storage/SessionUtil.java index 5d92d617b7..40f673dd44 100644 --- a/library/src/org/whispersystems/textsecure/storage/SessionUtil.java +++ b/library/src/org/whispersystems/textsecure/storage/SessionUtil.java @@ -35,9 +35,7 @@ public class SessionUtil { int deviceId = recipientDevice.getDeviceId(); SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); - return - sessionStore.containsSession(recipientId, deviceId) && - !sessionStore.loadSession(recipientId, deviceId).getSessionState().getNeedsRefresh(); + return sessionStore.containsSession(recipientId, deviceId); } }