diff options
Diffstat (limited to 'src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java')
-rw-r--r-- | src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java b/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java new file mode 100644 index 00000000..802f401d --- /dev/null +++ b/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java @@ -0,0 +1,601 @@ +package org.whispersystems.test; + +import android.test.AndroidTestCase; + +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.StaleKeyExchangeException; +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.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; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.libaxolotl.util.Pair; + +import java.util.HashSet; +import java.util.Set; + +public class SessionBuilderTest extends AndroidTestCase { + + private static final long ALICE_RECIPIENT_ID = 5L; + private static final long BOB_RECIPIENT_ID = 2L; + + public void testBasicPreKeyV2() + throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException { + 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(bobStore.getLocalRegistrationId(), 1, + 31337, bobPreKeyPair.getPublicKey(), + 0, null, null, + bobStore.getIdentityKeyPair().getPublicKey()); + + aliceSessionBuilder.process(bobPreKey); + + 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(aliceStore, BOB_RECIPIENT_ID, 1); + CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); + + assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); + + PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize()); + bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); + + 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()); + assertTrue(bobOutgoingMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] alicePlaintext = aliceSessionCipher.decrypt((WhisperMessage)bobOutgoingMessage); + assertTrue(new String(alicePlaintext).equals(originalMessage)); + + runInteraction(aliceStore, bobStore); + + 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(bobStore.getLocalRegistrationId(), + 1, 31338, bobPreKeyPair.getPublicKey(), + 0, null, null, bobStore.getIdentityKeyPair().getPublicKey()); + + bobStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + aliceSessionBuilder.process(bobPreKey); + + outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); + + try { + bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize())); + throw new AssertionError("shouldn't be trusted!"); + } catch (UntrustedIdentityException uie) { + 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(bobStore.getLocalRegistrationId(), 1, + 31337, Curve.generateKeyPair().getPublicKey(), + 0, null, null, + aliceStore.getIdentityKeyPair().getPublicKey()); + + try { + aliceSessionBuilder.process(bobPreKey); + throw new AssertionError("shoulnd't be trusted!"); + } catch (UntrustedIdentityException uie) { + // good + } + } + + public void testBasicPreKeyV3() + throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + + final AxolotlStore bobStore = new InMemoryAxolotlStore(); + ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); + ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), + bobSignedPreKeyPair.getPublicKey().serialize()); + + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, + 31337, bobPreKeyPair.getPublicKey(), + 22, bobSignedPreKeyPair.getPublicKey(), + bobSignedPreKeySignature, + bobStore.getIdentityKeyPair().getPublicKey()); + + aliceSessionBuilder.process(bobPreKey); + + assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1)); + assertTrue(aliceStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); + + final String originalMessage = "L'homme est condamné à être libre"; + 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()); + bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); + + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + byte[] plaintext = bobSessionCipher.decrypt(incomingMessage, new SessionCipher.DecryptionCallback() { + @Override + public void handlePlaintext(byte[] plaintext) { + assertTrue(originalMessage.equals(new String(plaintext))); + assertFalse(bobStore.containsSession(ALICE_RECIPIENT_ID, 1)); + } + }); + + 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()); + assertTrue(bobOutgoingMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize())); + assertTrue(new String(alicePlaintext).equals(originalMessage)); + + runInteraction(aliceStore, bobStore); + + 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(bobStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize()); + bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), + 1, 31338, bobPreKeyPair.getPublicKey(), + 23, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, + bobStore.getIdentityKeyPair().getPublicKey()); + + 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()); + + try { + plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize())); + throw new AssertionError("shouldn't be trusted!"); + } catch (UntrustedIdentityException uie) { + 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(bobStore.getLocalRegistrationId(), 1, + 31337, Curve.generateKeyPair().getPublicKey(), + 23, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, + aliceStore.getIdentityKeyPair().getPublicKey()); + + try { + aliceSessionBuilder.process(bobPreKey); + throw new AssertionError("shoulnd't be trusted!"); + } catch (UntrustedIdentityException uie) { + // good + } + } + + public void testBadSignedPreKeySignature() throws InvalidKeyException, UntrustedIdentityException { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + + IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); + + ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); + ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), + bobSignedPreKeyPair.getPublicKey().serialize()); + + + for (int i=0;i<bobSignedPreKeySignature.length * 8;i++) { + byte[] modifiedSignature = new byte[bobSignedPreKeySignature.length]; + System.arraycopy(bobSignedPreKeySignature, 0, modifiedSignature, 0, modifiedSignature.length); + + modifiedSignature[i/8] ^= (0x01 << (i % 8)); + + PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + 31337, bobPreKeyPair.getPublicKey(), + 22, bobSignedPreKeyPair.getPublicKey(), modifiedSignature, + bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + + try { + aliceSessionBuilder.process(bobPreKey); + throw new AssertionError("Accepted modified device key signature!"); + } catch (InvalidKeyException ike) { + // good + } + } + + PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, + 31337, bobPreKeyPair.getPublicKey(), + 22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, + bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); + + aliceSessionBuilder.process(bobPreKey); + } + + public void testRepeatBundleMessageV2() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, NoSessionException { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + + AxolotlStore bobStore = new InMemoryAxolotlStore(); + + ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); + ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), + bobSignedPreKeyPair.getPublicKey().serialize()); + + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, + 31337, bobPreKeyPair.getPublicKey(), + 0, null, null, + bobStore.getIdentityKeyPair().getPublicKey()); + + 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(aliceStore, BOB_RECIPIENT_ID, 1); + CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes()); + CiphertextMessage outgoingMessageTwo = aliceSessionCipher.encrypt(originalMessage.getBytes()); + + assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE); + + PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessageOne.serialize()); + + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + + byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); + assertTrue(originalMessage.equals(new String(plaintext))); + + CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); + + byte[] alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize())); + assertTrue(originalMessage.equals(new String(alicePlaintext))); + + // The test + + PreKeyWhisperMessage incomingMessageTwo = new PreKeyWhisperMessage(outgoingMessageTwo.serialize()); + + plaintext = bobSessionCipher.decrypt(incomingMessageTwo); + assertTrue(originalMessage.equals(new String(plaintext))); + + bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); + alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize())); + assertTrue(originalMessage.equals(new String(alicePlaintext))); + + } + + public void testRepeatBundleMessageV3() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, NoSessionException { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + + AxolotlStore bobStore = new InMemoryAxolotlStore(); + + ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); + ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), + bobSignedPreKeyPair.getPublicKey().serialize()); + + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, + 31337, bobPreKeyPair.getPublicKey(), + 22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, + bobStore.getIdentityKeyPair().getPublicKey()); + + 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(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(bobStore, ALICE_RECIPIENT_ID, 1); + + byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); + assertTrue(originalMessage.equals(new String(plaintext))); + + CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); + + byte[] alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize())); + assertTrue(originalMessage.equals(new String(alicePlaintext))); + + // The test + + PreKeyWhisperMessage incomingMessageTwo = new PreKeyWhisperMessage(outgoingMessageTwo.serialize()); + + plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(incomingMessageTwo.serialize())); + assertTrue(originalMessage.equals(new String(plaintext))); + + bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); + alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize())); + assertTrue(originalMessage.equals(new String(alicePlaintext))); + + } + + public void testBadMessageBundle() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + + AxolotlStore bobStore = new InMemoryAxolotlStore(); + + ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); + ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), + bobSignedPreKeyPair.getPublicKey().serialize()); + + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, + 31337, bobPreKeyPair.getPublicKey(), + 22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, + bobStore.getIdentityKeyPair().getPublicKey()); + + 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(aliceStore, BOB_RECIPIENT_ID, 1); + CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes()); + + assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE); + + byte[] goodMessage = outgoingMessageOne.serialize(); + byte[] badMessage = new byte[goodMessage.length]; + System.arraycopy(goodMessage, 0, badMessage, 0, badMessage.length); + + badMessage[badMessage.length-10] ^= 0x01; + + PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(badMessage); + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + + byte[] plaintext = new byte[0]; + + try { + plaintext = bobSessionCipher.decrypt(incomingMessage); + throw new AssertionError("Decrypt should have failed!"); + } catch (InvalidMessageException e) { + // good. + } + + assertTrue(bobStore.containsPreKey(31337)); + + plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(goodMessage)); + + assertTrue(originalMessage.equals(new String(plaintext))); + assertTrue(!bobStore.containsPreKey(31337)); + } + + public void testBasicKeyExchange() throws InvalidKeyException, LegacyMessageException, InvalidMessageException, DuplicateMessageException, UntrustedIdentityException, StaleKeyExchangeException, InvalidVersionException, NoSessionException { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + + AxolotlStore bobStore = new InMemoryAxolotlStore(); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1); + + KeyExchangeMessage aliceKeyExchangeMessage = aliceSessionBuilder.process(); + assertTrue(aliceKeyExchangeMessage != null); + + byte[] aliceKeyExchangeMessageBytes = aliceKeyExchangeMessage.serialize(); + KeyExchangeMessage bobKeyExchangeMessage = bobSessionBuilder.process(new KeyExchangeMessage(aliceKeyExchangeMessageBytes)); + + assertTrue(bobKeyExchangeMessage != null); + + byte[] bobKeyExchangeMessageBytes = bobKeyExchangeMessage.serialize(); + KeyExchangeMessage response = aliceSessionBuilder.process(new KeyExchangeMessage(bobKeyExchangeMessageBytes)); + + assertTrue(response == null); + assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1)); + assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1)); + + runInteraction(aliceStore, bobStore); + + 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) { + bobStore.saveIdentity(ALICE_RECIPIENT_ID, aliceKeyExchangeMessage.getIdentityKey()); + bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage); + } + + assertTrue(aliceSessionBuilder.process(bobKeyExchangeMessage) == null); + + runInteraction(aliceStore, bobStore); + } + + public void testSimultaneousKeyExchange() + throws InvalidKeyException, DuplicateMessageException, LegacyMessageException, InvalidMessageException, UntrustedIdentityException, StaleKeyExchangeException, NoSessionException { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + + AxolotlStore bobStore = new InMemoryAxolotlStore(); + SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1); + + KeyExchangeMessage aliceKeyExchange = aliceSessionBuilder.process(); + KeyExchangeMessage bobKeyExchange = bobSessionBuilder.process(); + + assertTrue(aliceKeyExchange != null); + assertTrue(bobKeyExchange != null); + + KeyExchangeMessage aliceResponse = aliceSessionBuilder.process(bobKeyExchange); + KeyExchangeMessage bobResponse = bobSessionBuilder.process(aliceKeyExchange); + + assertTrue(aliceResponse != null); + assertTrue(bobResponse != null); + + KeyExchangeMessage aliceAck = aliceSessionBuilder.process(bobResponse); + KeyExchangeMessage bobAck = bobSessionBuilder.process(aliceResponse); + + assertTrue(aliceAck == null); + assertTrue(bobAck == null); + + runInteraction(aliceStore, bobStore); + } + + public void testOptionalOneTimePreKey() throws Exception { + AxolotlStore aliceStore = new InMemoryAxolotlStore(); + SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1); + + AxolotlStore bobStore = new InMemoryAxolotlStore(); + + ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); + ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); + byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), + bobSignedPreKeyPair.getPublicKey().serialize()); + + PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1, + 0, null, + 22, bobSignedPreKeyPair.getPublicKey(), + bobSignedPreKeySignature, + bobStore.getIdentityKeyPair().getPublicKey()); + + aliceSessionBuilder.process(bobPreKey); + + 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(aliceStore, BOB_RECIPIENT_ID, 1); + CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); + + assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); + + PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize()); + assertTrue(!incomingMessage.getPreKeyId().isPresent()); + + bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); + bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); + + SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1); + byte[] plaintext = bobSessionCipher.decrypt(incomingMessage); + + 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(AxolotlStore aliceStore, AxolotlStore bobStore) + throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, NoSessionException + { + 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()); + + assertTrue(aliceMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] plaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceMessage.serialize())); + assertTrue(new String(plaintext).equals(originalMessage)); + + CiphertextMessage bobMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); + + assertTrue(bobMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + plaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobMessage.serialize())); + assertTrue(new String(plaintext).equals(originalMessage)); + + for (int i=0;i<10;i++) { + String loopingMessage = ("What do we mean by saying that existence precedes essence? " + + "We mean that man first of all exists, encounters himself, " + + "surges up in the world--and defines himself aftward. " + i); + CiphertextMessage aliceLoopingMessage = aliceSessionCipher.encrypt(loopingMessage.getBytes()); + + byte[] loopingPlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceLoopingMessage.serialize())); + assertTrue(new String(loopingPlaintext).equals(loopingMessage)); + } + + for (int i=0;i<10;i++) { + String loopingMessage = ("What do we mean by saying that existence precedes essence? " + + "We mean that man first of all exists, encounters himself, " + + "surges up in the world--and defines himself aftward. " + i); + CiphertextMessage bobLoopingMessage = bobSessionCipher.encrypt(loopingMessage.getBytes()); + + byte[] loopingPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobLoopingMessage.serialize())); + assertTrue(new String(loopingPlaintext).equals(loopingMessage)); + } + + Set<Pair<String, CiphertextMessage>> aliceOutOfOrderMessages = new HashSet<>(); + + for (int i=0;i<10;i++) { + String loopingMessage = ("What do we mean by saying that existence precedes essence? " + + "We mean that man first of all exists, encounters himself, " + + "surges up in the world--and defines himself aftward. " + i); + CiphertextMessage aliceLoopingMessage = aliceSessionCipher.encrypt(loopingMessage.getBytes()); + + aliceOutOfOrderMessages.add(new Pair<>(loopingMessage, aliceLoopingMessage)); + } + + for (int i=0;i<10;i++) { + String loopingMessage = ("What do we mean by saying that existence precedes essence? " + + "We mean that man first of all exists, encounters himself, " + + "surges up in the world--and defines himself aftward. " + i); + CiphertextMessage aliceLoopingMessage = aliceSessionCipher.encrypt(loopingMessage.getBytes()); + + byte[] loopingPlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceLoopingMessage.serialize())); + assertTrue(new String(loopingPlaintext).equals(loopingMessage)); + } + + for (int i=0;i<10;i++) { + String loopingMessage = ("You can only desire based on what you know: " + i); + CiphertextMessage bobLoopingMessage = bobSessionCipher.encrypt(loopingMessage.getBytes()); + + byte[] loopingPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobLoopingMessage.serialize())); + assertTrue(new String(loopingPlaintext).equals(loopingMessage)); + } + + for (Pair<String, CiphertextMessage> aliceOutOfOrderMessage : aliceOutOfOrderMessages) { + byte[] outOfOrderPlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceOutOfOrderMessage.second().serialize())); + assertTrue(new String(outOfOrderPlaintext).equals(aliceOutOfOrderMessage.first())); + } + } + + +} |