package org.whispersystems.libaxolotl; import junit.framework.TestCase; 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 TestCase { 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> 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 aliceOutOfOrderMessage : aliceOutOfOrderMessages) { byte[] outOfOrderPlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceOutOfOrderMessage.second().serialize())); assertTrue(new String(outOfOrderPlaintext).equals(aliceOutOfOrderMessage.first())); } } }