Introduce new simultaneous initiate strategy.

1) Fix bugs that prevented decrypt() from being non-transactional
   in some cases.

2) Introduce a new unified storage interface.

3) Transition simultaneous initiate from the "needs refresh"
   strategy to one that uses session state resurrection and
   promotion.
This commit is contained in:
Moxie Marlinspike 2014-09-07 15:08:15 -07:00
parent 73b75a4a27
commit 355d0be78a
10 changed files with 824 additions and 293 deletions

View File

@ -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<Integer> 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<SignedPreKeyRecord> 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);
}
}

View File

@ -20,6 +20,7 @@ import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage; import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage; import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.IdentityKeyStore; import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyBundle; import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.PreKeyRecord;
@ -39,46 +40,35 @@ public class SessionBuilderTest extends AndroidTestCase {
public void testBasicPreKeyV2() public void testBasicPreKeyV2()
throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException { throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException {
SessionStore aliceSessionStore = new InMemorySessionStore(); AxolotlStore aliceStore = new InMemoryAxolotlStore();
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
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 bobStore = new InMemoryAxolotlStore();
ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
31337, bobPreKeyPair.getPublicKey(), 31337, bobPreKeyPair.getPublicKey(),
0, null, null, 0, null, null,
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); bobStore.getIdentityKeyPair().getPublicKey());
aliceSessionBuilder.process(bobPreKey); aliceSessionBuilder.process(bobPreKey);
assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1)); assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1));
assertTrue(!aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh()); assertTrue(aliceStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2);
assertTrue(aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2);
String originalMessage = "L'homme est condamné à être libre"; 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()); CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE);
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize()); 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); byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1)); assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1));
assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2); assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2);
assertTrue(originalMessage.equals(new String(plaintext))); assertTrue(originalMessage.equals(new String(plaintext)));
CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes());
@ -87,23 +77,18 @@ public class SessionBuilderTest extends AndroidTestCase {
byte[] alicePlaintext = aliceSessionCipher.decrypt((WhisperMessage)bobOutgoingMessage); byte[] alicePlaintext = aliceSessionCipher.decrypt((WhisperMessage)bobOutgoingMessage);
assertTrue(new String(alicePlaintext).equals(originalMessage)); assertTrue(new String(alicePlaintext).equals(originalMessage));
runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, runInteraction(aliceStore, bobStore);
bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore);
aliceSessionStore = new InMemorySessionStore(); aliceStore = new InMemoryAxolotlStore();
aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
bobPreKeyPair = Curve.generateKeyPair(); bobPreKeyPair = Curve.generateKeyPair();
bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(),
1, 31338, bobPreKeyPair.getPublicKey(), 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); aliceSessionBuilder.process(bobPreKey);
outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
@ -112,17 +97,17 @@ public class SessionBuilderTest extends AndroidTestCase {
bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize())); bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()));
throw new AssertionError("shouldn't be trusted!"); throw new AssertionError("shouldn't be trusted!");
} catch (UntrustedIdentityException uie) { } 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())); plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()));
assertTrue(new String(plaintext).equals(originalMessage)); assertTrue(new String(plaintext).equals(originalMessage));
bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
31337, Curve.generateKeyPair().getPublicKey(), 31337, Curve.generateKeyPair().getPublicKey(),
0, null, null, 0, null, null,
aliceIdentityKeyStore.getIdentityKeyPair().getPublicKey()); aliceStore.getIdentityKeyPair().getPublicKey());
try { try {
aliceSessionBuilder.process(bobPreKey); aliceSessionBuilder.process(bobPreKey);
@ -134,54 +119,43 @@ public class SessionBuilderTest extends AndroidTestCase {
public void testBasicPreKeyV3() public void testBasicPreKeyV3()
throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException { throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException {
SessionStore aliceSessionStore = new InMemorySessionStore(); AxolotlStore aliceStore = new InMemoryAxolotlStore();
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
SessionStore bobSessionStore = new InMemorySessionStore(); AxolotlStore bobStore = new InMemoryAxolotlStore();
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore(); ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(),
bobSignedPreKeyPair.getPublicKey().serialize());
ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(),
bobSignedPreKeyPair.getPublicKey().serialize());
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1,
31337, bobPreKeyPair.getPublicKey(), 31337, bobPreKeyPair.getPublicKey(),
22, bobSignedPreKeyPair.getPublicKey(), 22, bobSignedPreKeyPair.getPublicKey(),
bobSignedPreKeySignature, bobSignedPreKeySignature,
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); bobStore.getIdentityKeyPair().getPublicKey());
aliceSessionBuilder.process(bobPreKey); aliceSessionBuilder.process(bobPreKey);
assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1)); assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1));
assertTrue(!aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh()); assertTrue(aliceStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
assertTrue(aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
String originalMessage = "L'homme est condamné à être libre"; 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()); CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE);
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize()); PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize());
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); 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); byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1)); assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1));
assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null); assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null);
assertTrue(originalMessage.equals(new String(plaintext))); assertTrue(originalMessage.equals(new String(plaintext)));
CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes());
@ -190,27 +164,22 @@ public class SessionBuilderTest extends AndroidTestCase {
byte[] alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize())); byte[] alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize()));
assertTrue(new String(alicePlaintext).equals(originalMessage)); assertTrue(new String(alicePlaintext).equals(originalMessage));
runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, runInteraction(aliceStore, bobStore);
bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore);
aliceSessionStore = new InMemorySessionStore(); aliceStore = new InMemoryAxolotlStore();
aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
bobPreKeyPair = Curve.generateKeyPair(); bobPreKeyPair = Curve.generateKeyPair();
bobSignedPreKeyPair = Curve.generateKeyPair(); bobSignedPreKeyPair = Curve.generateKeyPair();
bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize()); bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize());
bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(),
1, 31338, bobPreKeyPair.getPublicKey(), 1, 31338, bobPreKeyPair.getPublicKey(),
23, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, 23, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature,
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); bobStore.getIdentityKeyPair().getPublicKey());
bobPreKeyStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); bobStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
bobSignedPreKeyStore.storeSignedPreKey(23, new SignedPreKeyRecord(23, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); bobStore.storeSignedPreKey(23, new SignedPreKeyRecord(23, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
aliceSessionBuilder.process(bobPreKey); aliceSessionBuilder.process(bobPreKey);
outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
@ -219,16 +188,16 @@ public class SessionBuilderTest extends AndroidTestCase {
plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize())); plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()));
throw new AssertionError("shouldn't be trusted!"); throw new AssertionError("shouldn't be trusted!");
} catch (UntrustedIdentityException uie) { } 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())); plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()));
assertTrue(new String(plaintext).equals(originalMessage)); assertTrue(new String(plaintext).equals(originalMessage));
bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
31337, Curve.generateKeyPair().getPublicKey(), 31337, Curve.generateKeyPair().getPublicKey(),
23, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, 23, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature,
aliceIdentityKeyStore.getIdentityKeyPair().getPublicKey()); aliceStore.getIdentityKeyPair().getPublicKey());
try { try {
aliceSessionBuilder.process(bobPreKey); aliceSessionBuilder.process(bobPreKey);
@ -239,14 +208,8 @@ public class SessionBuilderTest extends AndroidTestCase {
} }
public void testBadSignedPreKeySignature() throws InvalidKeyException, UntrustedIdentityException { public void testBadSignedPreKeySignature() throws InvalidKeyException, UntrustedIdentityException {
SessionStore aliceSessionStore = new InMemorySessionStore(); AxolotlStore aliceStore = new InMemoryAxolotlStore();
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore(); 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 { public void testRepeatBundleMessageV2() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, NoSessionException {
SessionStore aliceSessionStore = new InMemorySessionStore(); AxolotlStore aliceStore = new InMemoryAxolotlStore();
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
SessionStore bobSessionStore = new InMemorySessionStore(); AxolotlStore bobStore = new InMemoryAxolotlStore();
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(),
bobSignedPreKeyPair.getPublicKey().serialize()); bobSignedPreKeyPair.getPublicKey().serialize());
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
31337, bobPreKeyPair.getPublicKey(), 31337, bobPreKeyPair.getPublicKey(),
0, null, null, 0, null, null,
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); bobStore.getIdentityKeyPair().getPublicKey());
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
aliceSessionBuilder.process(bobPreKey); aliceSessionBuilder.process(bobPreKey);
String originalMessage = "L'homme est condamné à être libre"; 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 outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes());
CiphertextMessage outgoingMessageTwo = 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()); 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); byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
assertTrue(originalMessage.equals(new String(plaintext))); 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 { public void testRepeatBundleMessageV3() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, NoSessionException {
SessionStore aliceSessionStore = new InMemorySessionStore(); AxolotlStore aliceStore = new InMemoryAxolotlStore();
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
SessionStore bobSessionStore = new InMemorySessionStore(); AxolotlStore bobStore = new InMemoryAxolotlStore();
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(),
bobSignedPreKeyPair.getPublicKey().serialize()); bobSignedPreKeyPair.getPublicKey().serialize());
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
31337, bobPreKeyPair.getPublicKey(), 31337, bobPreKeyPair.getPublicKey(),
22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, 22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature,
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); bobStore.getIdentityKeyPair().getPublicKey());
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
aliceSessionBuilder.process(bobPreKey); aliceSessionBuilder.process(bobPreKey);
String originalMessage = "L'homme est condamné à être libre"; 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 outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes());
CiphertextMessage outgoingMessageTwo = aliceSessionCipher.encrypt(originalMessage.getBytes()); CiphertextMessage outgoingMessageTwo = aliceSessionCipher.encrypt(originalMessage.getBytes());
assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE);
assertTrue(outgoingMessageTwo.getType() == CiphertextMessage.PREKEY_TYPE);
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessageOne.serialize()); 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); byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
assertTrue(originalMessage.equals(new String(plaintext))); 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 { public void testBadMessageBundle() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException {
SessionStore aliceSessionStore = new InMemorySessionStore(); AxolotlStore aliceStore = new InMemoryAxolotlStore();
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
SessionStore bobSessionStore = new InMemorySessionStore(); AxolotlStore bobStore = new InMemoryAxolotlStore();
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(),
bobSignedPreKeyPair.getPublicKey().serialize()); bobSignedPreKeyPair.getPublicKey().serialize());
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
31337, bobPreKeyPair.getPublicKey(), 31337, bobPreKeyPair.getPublicKey(),
22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature, 22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature,
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); bobStore.getIdentityKeyPair().getPublicKey());
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
aliceSessionBuilder.process(bobPreKey); aliceSessionBuilder.process(bobPreKey);
String originalMessage = "L'homme est condamné à être libre"; 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 outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes());
assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE);
@ -450,7 +387,7 @@ public class SessionBuilderTest extends AndroidTestCase {
badMessage[badMessage.length-10] ^= 0x01; badMessage[badMessage.length-10] ^= 0x01;
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(badMessage); 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]; byte[] plaintext = new byte[0];
@ -461,32 +398,20 @@ public class SessionBuilderTest extends AndroidTestCase {
// good. // good.
} }
assertTrue(bobPreKeyStore.containsPreKey(31337)); assertTrue(bobStore.containsPreKey(31337));
plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(goodMessage)); plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(goodMessage));
assertTrue(originalMessage.equals(new String(plaintext))); 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 { public void testBasicKeyExchange() throws InvalidKeyException, LegacyMessageException, InvalidMessageException, DuplicateMessageException, UntrustedIdentityException, StaleKeyExchangeException, InvalidVersionException, NoSessionException {
SessionStore aliceSessionStore = new InMemorySessionStore(); AxolotlStore aliceStore = new InMemoryAxolotlStore();
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
SessionStore bobSessionStore = new InMemorySessionStore(); AxolotlStore bobStore = new InMemoryAxolotlStore();
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1);
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder bobSessionBuilder = new SessionBuilder(bobSessionStore, bobPreKeyStore,
bobSignedPreKeyStore,
bobIdentityKeyStore,
ALICE_RECIPIENT_ID, 1);
KeyExchangeMessage aliceKeyExchangeMessage = aliceSessionBuilder.process(); KeyExchangeMessage aliceKeyExchangeMessage = aliceSessionBuilder.process();
assertTrue(aliceKeyExchangeMessage != null); assertTrue(aliceKeyExchangeMessage != null);
@ -500,52 +425,35 @@ public class SessionBuilderTest extends AndroidTestCase {
KeyExchangeMessage response = aliceSessionBuilder.process(new KeyExchangeMessage(bobKeyExchangeMessageBytes)); KeyExchangeMessage response = aliceSessionBuilder.process(new KeyExchangeMessage(bobKeyExchangeMessageBytes));
assertTrue(response == null); assertTrue(response == null);
assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1)); assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1));
assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1)); assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1));
runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, runInteraction(aliceStore, bobStore);
bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore);
aliceSessionStore = new InMemorySessionStore(); aliceStore = new InMemoryAxolotlStore();
aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
aliceKeyExchangeMessage = aliceSessionBuilder.process(); aliceKeyExchangeMessage = aliceSessionBuilder.process();
try { try {
bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage); bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage);
throw new AssertionError("This identity shouldn't be trusted!"); throw new AssertionError("This identity shouldn't be trusted!");
} catch (UntrustedIdentityException uie) { } catch (UntrustedIdentityException uie) {
bobIdentityKeyStore.saveIdentity(ALICE_RECIPIENT_ID, aliceKeyExchangeMessage.getIdentityKey()); bobStore.saveIdentity(ALICE_RECIPIENT_ID, aliceKeyExchangeMessage.getIdentityKey());
bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage); bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage);
} }
assertTrue(aliceSessionBuilder.process(bobKeyExchangeMessage) == null); assertTrue(aliceSessionBuilder.process(bobKeyExchangeMessage) == null);
runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, runInteraction(aliceStore, bobStore);
bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore);
} }
public void testSimultaneousKeyExchange() public void testSimultaneousKeyExchange()
throws InvalidKeyException, DuplicateMessageException, LegacyMessageException, InvalidMessageException, UntrustedIdentityException, StaleKeyExchangeException, NoSessionException { throws InvalidKeyException, DuplicateMessageException, LegacyMessageException, InvalidMessageException, UntrustedIdentityException, StaleKeyExchangeException, NoSessionException {
SessionStore aliceSessionStore = new InMemorySessionStore(); AxolotlStore aliceStore = new InMemoryAxolotlStore();
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
SessionStore bobSessionStore = new InMemorySessionStore(); AxolotlStore bobStore = new InMemoryAxolotlStore();
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore(); SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1);
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder bobSessionBuilder = new SessionBuilder(bobSessionStore, bobPreKeyStore,
bobSignedPreKeyStore,
bobIdentityKeyStore,
ALICE_RECIPIENT_ID, 1);
KeyExchangeMessage aliceKeyExchange = aliceSessionBuilder.process(); KeyExchangeMessage aliceKeyExchange = aliceSessionBuilder.process();
KeyExchangeMessage bobKeyExchange = bobSessionBuilder.process(); KeyExchangeMessage bobKeyExchange = bobSessionBuilder.process();
@ -565,44 +473,33 @@ public class SessionBuilderTest extends AndroidTestCase {
assertTrue(aliceAck == null); assertTrue(aliceAck == null);
assertTrue(bobAck == null); assertTrue(bobAck == null);
runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, runInteraction(aliceStore, bobStore);
bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore);
} }
public void testOptionalOneTimePreKey() throws Exception { public void testOptionalOneTimePreKey() throws Exception {
SessionStore aliceSessionStore = new InMemorySessionStore(); AxolotlStore aliceStore = new InMemoryAxolotlStore();
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
SessionStore bobSessionStore = new InMemorySessionStore(); AxolotlStore bobStore = new InMemoryAxolotlStore();
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
ECKeyPair bobPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(); ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(),
bobSignedPreKeyPair.getPublicKey().serialize()); bobSignedPreKeyPair.getPublicKey().serialize());
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1, PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
0, null, 0, null,
22, bobSignedPreKeyPair.getPublicKey(), 22, bobSignedPreKeyPair.getPublicKey(),
bobSignedPreKeySignature, bobSignedPreKeySignature,
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey()); bobStore.getIdentityKeyPair().getPublicKey());
aliceSessionBuilder.process(bobPreKey); aliceSessionBuilder.process(bobPreKey);
assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1)); assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1));
assertTrue(!aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh()); assertTrue(aliceStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
assertTrue(aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
String originalMessage = "L'homme est condamné à être libre"; 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()); CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE);
@ -610,31 +507,24 @@ public class SessionBuilderTest extends AndroidTestCase {
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize()); PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize());
assertTrue(!incomingMessage.getPreKeyId().isPresent()); assertTrue(!incomingMessage.getPreKeyId().isPresent());
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair)); bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature)); 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); byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1)); assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1));
assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3); assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null); assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null);
assertTrue(originalMessage.equals(new String(plaintext))); assertTrue(originalMessage.equals(new String(plaintext)));
} }
private void runInteraction(SessionStore aliceSessionStore, private void runInteraction(AxolotlStore aliceStore, AxolotlStore bobStore)
PreKeyStore alicePreKeyStore,
SignedPreKeyStore aliceSignedPreKeyStore,
IdentityKeyStore aliceIdentityKeyStore,
SessionStore bobSessionStore,
PreKeyStore bobPreKeyStore,
SignedPreKeyStore bobSignedPreKeyStore,
IdentityKeyStore bobIdentityKeyStore)
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, NoSessionException throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, NoSessionException
{ {
SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1);
String originalMessage = "smert ze smert"; String originalMessage = "smert ze smert";
CiphertextMessage aliceMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); CiphertextMessage aliceMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());

View File

@ -18,6 +18,7 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.ratchet.AliceAxolotlParameters; import org.whispersystems.libaxolotl.ratchet.AliceAxolotlParameters;
import org.whispersystems.libaxolotl.ratchet.BobAxolotlParameters; import org.whispersystems.libaxolotl.ratchet.BobAxolotlParameters;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession; import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.IdentityKeyStore; import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyStore; import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SessionRecord;
@ -60,20 +61,14 @@ public class SessionCipherTest extends AndroidTestCase {
private void runInteraction(SessionRecord aliceSessionRecord, SessionRecord bobSessionRecord) private void runInteraction(SessionRecord aliceSessionRecord, SessionRecord bobSessionRecord)
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, NoSuchAlgorithmException, NoSessionException { throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, NoSuchAlgorithmException, NoSessionException {
SessionStore aliceSessionStore = new InMemorySessionStore(); AxolotlStore aliceStore = new InMemoryAxolotlStore();
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); AxolotlStore bobStore = new InMemoryAxolotlStore();
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionStore bobSessionStore = new InMemorySessionStore();
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
aliceSessionStore.storeSession(2L, 1, aliceSessionRecord); aliceStore.storeSession(2L, 1, aliceSessionRecord);
bobSessionStore.storeSession(3L, 1, bobSessionRecord); bobStore.storeSession(3L, 1, bobSessionRecord);
SessionCipher aliceCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, 2L, 1); SessionCipher aliceCipher = new SessionCipher(aliceStore, 2L, 1);
SessionCipher bobCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, 3L, 1); SessionCipher bobCipher = new SessionCipher(bobStore, 3L, 1);
byte[] alicePlaintext = "This is a plaintext message.".getBytes(); byte[] alicePlaintext = "This is a plaintext message.".getBytes();
CiphertextMessage message = aliceCipher.encrypt(alicePlaintext); CiphertextMessage message = aliceCipher.encrypt(alicePlaintext);

View File

@ -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;
}
}

View File

@ -12,6 +12,7 @@ import org.whispersystems.libaxolotl.ratchet.AliceAxolotlParameters;
import org.whispersystems.libaxolotl.ratchet.BobAxolotlParameters; import org.whispersystems.libaxolotl.ratchet.BobAxolotlParameters;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession; import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
import org.whispersystems.libaxolotl.ratchet.SymmetricAxolotlParameters; import org.whispersystems.libaxolotl.ratchet.SymmetricAxolotlParameters;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.IdentityKeyStore; import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyBundle; import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.PreKeyStore; import org.whispersystems.libaxolotl.state.PreKeyStore;
@ -74,6 +75,16 @@ public class SessionBuilder {
this.deviceId = deviceId; 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}. * Build a new session from a received {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}.
* *
@ -119,8 +130,7 @@ public class SessionBuilder {
return Optional.absent(); 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(); BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder();
@ -136,8 +146,7 @@ public class SessionBuilder {
parameters.setOurOneTimePreKey(Optional.<ECKeyPair>absent()); parameters.setOurOneTimePreKey(Optional.<ECKeyPair>absent());
} }
if (!simultaneousInitiate) sessionRecord.reset(); if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
else sessionRecord.archiveCurrentState();
RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create()); RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create());
@ -145,8 +154,6 @@ public class SessionBuilder {
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize()); sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize());
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
if (message.getPreKeyId().isPresent() && message.getPreKeyId().get() != Medium.MAX_VALUE) { if (message.getPreKeyId().isPresent() && message.getPreKeyId().get() != Medium.MAX_VALUE) {
return message.getPreKeyId(); return message.getPreKeyId();
} else { } else {
@ -168,8 +175,7 @@ public class SessionBuilder {
return Optional.absent(); return Optional.absent();
} }
ECKeyPair ourPreKey = preKeyStore.loadPreKey(message.getPreKeyId().get()).getKeyPair(); ECKeyPair ourPreKey = preKeyStore.loadPreKey(message.getPreKeyId().get()).getKeyPair();
boolean simultaneousInitiate = sessionRecord.getSessionState().hasUnacknowledgedPreKeyMessage();
BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder(); BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder();
@ -180,15 +186,13 @@ public class SessionBuilder {
.setTheirIdentityKey(message.getIdentityKey()) .setTheirIdentityKey(message.getIdentityKey())
.setTheirBaseKey(message.getBaseKey()); .setTheirBaseKey(message.getBaseKey());
if (!simultaneousInitiate) sessionRecord.reset(); if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
else sessionRecord.archiveCurrentState();
RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create()); RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create());
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId()); sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize());
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
if (message.getPreKeyId().get() != Medium.MAX_VALUE) { if (message.getPreKeyId().get() != Medium.MAX_VALUE) {
return message.getPreKeyId(); return message.getPreKeyId();
@ -227,7 +231,6 @@ public class SessionBuilder {
} }
boolean supportsV3 = preKey.getSignedPreKey() != null; boolean supportsV3 = preKey.getSignedPreKey() != null;
boolean isExistingSession = sessionStore.containsSession(recipientId, deviceId);
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId); SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
ECKeyPair ourBaseKey = Curve.generateKeyPair(); ECKeyPair ourBaseKey = Curve.generateKeyPair();
ECPublicKey theirSignedPreKey = supportsV3 ? preKey.getSignedPreKey() : preKey.getPreKey(); ECPublicKey theirSignedPreKey = supportsV3 ? preKey.getSignedPreKey() : preKey.getPreKey();
@ -244,8 +247,7 @@ public class SessionBuilder {
.setTheirRatchetKey(theirSignedPreKey) .setTheirRatchetKey(theirSignedPreKey)
.setTheirOneTimePreKey(supportsV3 ? theirOneTimePreKey : Optional.<ECPublicKey>absent()); .setTheirOneTimePreKey(supportsV3 ? theirOneTimePreKey : Optional.<ECPublicKey>absent());
if (isExistingSession) sessionRecord.archiveCurrentState(); if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
else sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(), RatchetingSession.initializeSession(sessionRecord.getSessionState(),
supportsV3 ? 3 : 2, supportsV3 ? 3 : 2,
@ -254,6 +256,7 @@ public class SessionBuilder {
sessionRecord.getSessionState().setUnacknowledgedPreKeyMessage(theirOneTimePreKeyId, preKey.getSignedPreKeyId(), ourBaseKey.getPublicKey()); sessionRecord.getSessionState().setUnacknowledgedPreKeyMessage(theirOneTimePreKeyId, preKey.getSignedPreKeyId(), ourBaseKey.getPublicKey());
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId()); sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
sessionRecord.getSessionState().setRemoteRegistrationId(preKey.getRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(preKey.getRegistrationId());
sessionRecord.getSessionState().setAliceBaseKey(ourBaseKey.getPublicKey().serialize());
sessionStore.storeSession(recipientId, deviceId, sessionRecord); sessionStore.storeSession(recipientId, deviceId, sessionRecord);
identityKeyStore.saveIdentity(recipientId, preKey.getIdentityKey()); identityKeyStore.saveIdentity(recipientId, preKey.getIdentityKey());
@ -316,7 +319,7 @@ public class SessionBuilder {
SymmetricAxolotlParameters parameters = builder.create(); SymmetricAxolotlParameters parameters = builder.create();
sessionRecord.reset(); if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
RatchetingSession.initializeSession(sessionRecord.getSessionState(), RatchetingSession.initializeSession(sessionRecord.getSessionState(),
Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION), Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION),
@ -358,7 +361,7 @@ public class SessionBuilder {
.setTheirRatchetKey(message.getRatchetKey()) .setTheirRatchetKey(message.getRatchetKey())
.setTheirIdentityKey(message.getIdentityKey()); .setTheirIdentityKey(message.getIdentityKey());
sessionRecord.reset(); if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
RatchetingSession.initializeSession(sessionRecord.getSessionState(), RatchetingSession.initializeSession(sessionRecord.getSessionState(),
Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION), Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION),

View File

@ -25,6 +25,7 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.ratchet.ChainKey; import org.whispersystems.libaxolotl.ratchet.ChainKey;
import org.whispersystems.libaxolotl.ratchet.MessageKeys; import org.whispersystems.libaxolotl.ratchet.MessageKeys;
import org.whispersystems.libaxolotl.ratchet.RootKey; import org.whispersystems.libaxolotl.ratchet.RootKey;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.state.IdentityKeyStore; import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyStore; import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SessionRecord;
@ -37,6 +38,7 @@ import org.whispersystems.libaxolotl.util.guava.Optional;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -89,6 +91,10 @@ public class SessionCipher {
identityKeyStore, recipientId, deviceId); identityKeyStore, recipientId, deviceId);
} }
public SessionCipher(AxolotlStore store, long recipientId, int deviceId) {
this(store, store, store, store, recipientId, deviceId);
}
/** /**
* Encrypt a message. * Encrypt a message.
* *
@ -198,19 +204,28 @@ public class SessionCipher {
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException throws DuplicateMessageException, LegacyMessageException, InvalidMessageException
{ {
synchronized (SESSION_LOCK) { synchronized (SESSION_LOCK) {
SessionState sessionState = sessionRecord.getSessionState(); Iterator<SessionState> previousStates = sessionRecord.getPreviousSessionStates().iterator();
List<SessionState> previousStates = sessionRecord.getPreviousSessionStates(); List<Exception> exceptions = new LinkedList<>();
List<Exception> exceptions = new LinkedList<>();
try { try {
return decrypt(sessionState, ciphertext); SessionState sessionState = new SessionState(sessionRecord.getSessionState());
byte[] plaintext = decrypt(sessionState, ciphertext);
sessionRecord.setState(sessionState);
return plaintext;
} catch (InvalidMessageException e) { } catch (InvalidMessageException e) {
exceptions.add(e); exceptions.add(e);
} }
for (SessionState previousState : previousStates) { while (previousStates.hasNext()) {
try { 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) { } catch (InvalidMessageException e) {
exceptions.add(e); exceptions.add(e);
} }

View File

@ -0,0 +1,6 @@
package org.whispersystems.libaxolotl.state;
public interface AxolotlStore
extends IdentityKeyStore, PreKeyStore, SessionStore, SignedPreKeyStore
{
}

View File

@ -15,18 +15,25 @@ import static org.whispersystems.libaxolotl.state.StorageProtos.SessionStructure
*/ */
public class SessionRecord { public class SessionRecord {
private SessionState sessionState = new SessionState(); private static final int ARCHIVED_STATES_MAX_LENGTH = 40;
private List<SessionState> previousStates = new LinkedList<>();
public SessionRecord() {} private SessionState sessionState = new SessionState();
private LinkedList<SessionState> previousStates = new LinkedList<>();
private boolean fresh = false;
public SessionRecord() {
this.fresh = true;
}
public SessionRecord(SessionState sessionState) { public SessionRecord(SessionState sessionState) {
this.sessionState = sessionState; this.sessionState = sessionState;
this.fresh = false;
} }
public SessionRecord(byte[] serialized) throws IOException { public SessionRecord(byte[] serialized) throws IOException {
RecordStructure record = RecordStructure.parseFrom(serialized); RecordStructure record = RecordStructure.parseFrom(serialized);
this.sessionState = new SessionState(record.getCurrentSession()); this.sessionState = new SessionState(record.getCurrentSession());
this.fresh = false;
for (SessionStructure previousStructure : record.getPreviousSessionsList()) { for (SessionStructure previousStructure : record.getPreviousSessionsList()) {
previousStates.add(new SessionState(previousStructure)); previousStates.add(new SessionState(previousStructure));
@ -62,14 +69,9 @@ public class SessionRecord {
return previousStates; return previousStates;
} }
/**
* Reset the current SessionRecord, clearing all "previous" session states, public boolean isFresh() {
* and resetting the current {@link org.whispersystems.libaxolotl.state.SessionState} return fresh;
* to a fresh state.
*/
public void reset() {
this.sessionState = new SessionState();
this.previousStates = new LinkedList<>();
} }
/** /**
@ -78,8 +80,20 @@ public class SessionRecord {
* with a fresh reset instance. * with a fresh reset instance.
*/ */
public void archiveCurrentState() { public void archiveCurrentState() {
this.previousStates.add(sessionState); promoteState(new SessionState());
this.sessionState = 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;
} }
/** /**

View File

@ -59,6 +59,10 @@ public class SessionState {
this.sessionStructure = sessionStructure; this.sessionStructure = sessionStructure;
} }
public SessionState(SessionState copy) {
this.sessionStructure = copy.sessionStructure.toBuilder().build();
}
public SessionStructure getStructure() { public SessionStructure getStructure() {
return sessionStructure; return sessionStructure;
} }
@ -73,16 +77,6 @@ public class SessionState {
.build(); .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) { public void setSessionVersion(int version) {
this.sessionStructure = this.sessionStructure.toBuilder() this.sessionStructure = this.sessionStructure.toBuilder()
.setSessionVersion(version) .setSessionVersion(version)

View File

@ -35,9 +35,7 @@ public class SessionUtil {
int deviceId = recipientDevice.getDeviceId(); int deviceId = recipientDevice.getDeviceId();
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
return return sessionStore.containsSession(recipientId, deviceId);
sessionStore.containsSession(recipientId, deviceId) &&
!sessionStore.loadSession(recipientId, deviceId).getSessionState().getNeedsRefresh();
} }
} }