mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
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:
parent
73b75a4a27
commit
355d0be78a
@ -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);
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
|
||||
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
||||
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
||||
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyBundle;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||
@ -39,46 +40,35 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
|
||||
public void testBasicPreKeyV2()
|
||||
throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
|
||||
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
|
||||
SessionStore bobSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
AxolotlStore aliceStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
AxolotlStore bobStore = new InMemoryAxolotlStore();
|
||||
ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1,
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
|
||||
31337, bobPreKeyPair.getPublicKey(),
|
||||
0, null, null,
|
||||
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey());
|
||||
bobStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
|
||||
assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1));
|
||||
assertTrue(!aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh());
|
||||
assertTrue(aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2);
|
||||
assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1));
|
||||
assertTrue(aliceStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2);
|
||||
|
||||
String originalMessage = "L'homme est condamné à être libre";
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
|
||||
assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE);
|
||||
|
||||
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize());
|
||||
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1);
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1);
|
||||
byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
|
||||
|
||||
assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1));
|
||||
assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2);
|
||||
assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1));
|
||||
assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 2);
|
||||
assertTrue(originalMessage.equals(new String(plaintext)));
|
||||
|
||||
CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes());
|
||||
@ -87,23 +77,18 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
byte[] alicePlaintext = aliceSessionCipher.decrypt((WhisperMessage)bobOutgoingMessage);
|
||||
assertTrue(new String(alicePlaintext).equals(originalMessage));
|
||||
|
||||
runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore,
|
||||
bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore);
|
||||
runInteraction(aliceStore, bobStore);
|
||||
|
||||
aliceSessionStore = new InMemorySessionStore();
|
||||
aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
|
||||
aliceStore = new InMemoryAxolotlStore();
|
||||
aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
bobPreKeyPair = Curve.generateKeyPair();
|
||||
bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(),
|
||||
bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(),
|
||||
1, 31338, bobPreKeyPair.getPublicKey(),
|
||||
0, null, null, bobIdentityKeyStore.getIdentityKeyPair().getPublicKey());
|
||||
0, null, null, bobStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
bobPreKeyStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
|
||||
outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
@ -112,17 +97,17 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()));
|
||||
throw new AssertionError("shouldn't be trusted!");
|
||||
} catch (UntrustedIdentityException uie) {
|
||||
bobIdentityKeyStore.saveIdentity(ALICE_RECIPIENT_ID, new PreKeyWhisperMessage(outgoingMessage.serialize()).getIdentityKey());
|
||||
bobStore.saveIdentity(ALICE_RECIPIENT_ID, new PreKeyWhisperMessage(outgoingMessage.serialize()).getIdentityKey());
|
||||
}
|
||||
|
||||
plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()));
|
||||
|
||||
assertTrue(new String(plaintext).equals(originalMessage));
|
||||
|
||||
bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1,
|
||||
bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
|
||||
31337, Curve.generateKeyPair().getPublicKey(),
|
||||
0, null, null,
|
||||
aliceIdentityKeyStore.getIdentityKeyPair().getPublicKey());
|
||||
aliceStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
try {
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
@ -134,54 +119,43 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
|
||||
public void testBasicPreKeyV3()
|
||||
throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException, NoSessionException {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
|
||||
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
|
||||
SessionStore bobSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
AxolotlStore aliceStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
AxolotlStore bobStore = new InMemoryAxolotlStore();
|
||||
ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
|
||||
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
|
||||
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(),
|
||||
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(),
|
||||
bobSignedPreKeyPair.getPublicKey().serialize());
|
||||
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1,
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
|
||||
31337, bobPreKeyPair.getPublicKey(),
|
||||
22, bobSignedPreKeyPair.getPublicKey(),
|
||||
bobSignedPreKeySignature,
|
||||
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey());
|
||||
bobStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
|
||||
assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1));
|
||||
assertTrue(!aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh());
|
||||
assertTrue(aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
|
||||
assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1));
|
||||
assertTrue(aliceStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
|
||||
|
||||
String originalMessage = "L'homme est condamné à être libre";
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
|
||||
assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE);
|
||||
|
||||
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize());
|
||||
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1);
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1);
|
||||
byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
|
||||
|
||||
|
||||
assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1));
|
||||
assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
|
||||
assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null);
|
||||
assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1));
|
||||
assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
|
||||
assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null);
|
||||
assertTrue(originalMessage.equals(new String(plaintext)));
|
||||
|
||||
CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes());
|
||||
@ -190,27 +164,22 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
byte[] alicePlaintext = aliceSessionCipher.decrypt(new WhisperMessage(bobOutgoingMessage.serialize()));
|
||||
assertTrue(new String(alicePlaintext).equals(originalMessage));
|
||||
|
||||
runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore,
|
||||
bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore);
|
||||
runInteraction(aliceStore, bobStore);
|
||||
|
||||
aliceSessionStore = new InMemorySessionStore();
|
||||
aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
|
||||
aliceStore = new InMemoryAxolotlStore();
|
||||
aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
bobPreKeyPair = Curve.generateKeyPair();
|
||||
bobSignedPreKeyPair = Curve.generateKeyPair();
|
||||
bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize());
|
||||
bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(),
|
||||
bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKeyPair.getPublicKey().serialize());
|
||||
bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(),
|
||||
1, 31338, bobPreKeyPair.getPublicKey(),
|
||||
23, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature,
|
||||
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey());
|
||||
bobStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
bobPreKeyStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobSignedPreKeyStore.storeSignedPreKey(23, new SignedPreKeyRecord(23, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
bobStore.storePreKey(31338, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobStore.storeSignedPreKey(23, new SignedPreKeyRecord(23, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
|
||||
outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
@ -219,16 +188,16 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()));
|
||||
throw new AssertionError("shouldn't be trusted!");
|
||||
} catch (UntrustedIdentityException uie) {
|
||||
bobIdentityKeyStore.saveIdentity(ALICE_RECIPIENT_ID, new PreKeyWhisperMessage(outgoingMessage.serialize()).getIdentityKey());
|
||||
bobStore.saveIdentity(ALICE_RECIPIENT_ID, new PreKeyWhisperMessage(outgoingMessage.serialize()).getIdentityKey());
|
||||
}
|
||||
|
||||
plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()));
|
||||
assertTrue(new String(plaintext).equals(originalMessage));
|
||||
|
||||
bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1,
|
||||
bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
|
||||
31337, Curve.generateKeyPair().getPublicKey(),
|
||||
23, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature,
|
||||
aliceIdentityKeyStore.getIdentityKeyPair().getPublicKey());
|
||||
aliceStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
try {
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
@ -239,14 +208,8 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
}
|
||||
|
||||
public void testBadSignedPreKeySignature() throws InvalidKeyException, UntrustedIdentityException {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
|
||||
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
AxolotlStore aliceStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
|
||||
@ -284,37 +247,28 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
}
|
||||
|
||||
public void testRepeatBundleMessageV2() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, NoSessionException {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
|
||||
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
AxolotlStore aliceStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
SessionStore bobSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
AxolotlStore bobStore = new InMemoryAxolotlStore();
|
||||
|
||||
ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
|
||||
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
|
||||
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(),
|
||||
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(),
|
||||
bobSignedPreKeyPair.getPublicKey().serialize());
|
||||
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1,
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
|
||||
31337, bobPreKeyPair.getPublicKey(),
|
||||
0, null, null,
|
||||
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey());
|
||||
bobStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
|
||||
String originalMessage = "L'homme est condamné à être libre";
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
CiphertextMessage outgoingMessageTwo = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
|
||||
@ -322,7 +276,7 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
|
||||
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessageOne.serialize());
|
||||
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1);
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1);
|
||||
|
||||
byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
|
||||
assertTrue(originalMessage.equals(new String(plaintext)));
|
||||
@ -346,45 +300,37 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
}
|
||||
|
||||
public void testRepeatBundleMessageV3() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, NoSessionException {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
|
||||
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
AxolotlStore aliceStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
SessionStore bobSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
AxolotlStore bobStore = new InMemoryAxolotlStore();
|
||||
|
||||
ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
|
||||
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
|
||||
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(),
|
||||
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(),
|
||||
bobSignedPreKeyPair.getPublicKey().serialize());
|
||||
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1,
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
|
||||
31337, bobPreKeyPair.getPublicKey(),
|
||||
22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature,
|
||||
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey());
|
||||
bobStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
|
||||
String originalMessage = "L'homme est condamné à être libre";
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
CiphertextMessage outgoingMessageTwo = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
|
||||
assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE);
|
||||
assertTrue(outgoingMessageTwo.getType() == CiphertextMessage.PREKEY_TYPE);
|
||||
|
||||
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessageOne.serialize());
|
||||
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1);
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1);
|
||||
|
||||
byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
|
||||
assertTrue(originalMessage.equals(new String(plaintext)));
|
||||
@ -408,37 +354,28 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
}
|
||||
|
||||
public void testBadMessageBundle() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
|
||||
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
AxolotlStore aliceStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
SessionStore bobSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
AxolotlStore bobStore = new InMemoryAxolotlStore();
|
||||
|
||||
ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
|
||||
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
|
||||
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(),
|
||||
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(),
|
||||
bobSignedPreKeyPair.getPublicKey().serialize());
|
||||
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1,
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
|
||||
31337, bobPreKeyPair.getPublicKey(),
|
||||
22, bobSignedPreKeyPair.getPublicKey(), bobSignedPreKeySignature,
|
||||
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey());
|
||||
bobStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
|
||||
String originalMessage = "L'homme est condamné à être libre";
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
CiphertextMessage outgoingMessageOne = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
|
||||
assertTrue(outgoingMessageOne.getType() == CiphertextMessage.PREKEY_TYPE);
|
||||
@ -450,7 +387,7 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
badMessage[badMessage.length-10] ^= 0x01;
|
||||
|
||||
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(badMessage);
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1);
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1);
|
||||
|
||||
byte[] plaintext = new byte[0];
|
||||
|
||||
@ -461,32 +398,20 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
// good.
|
||||
}
|
||||
|
||||
assertTrue(bobPreKeyStore.containsPreKey(31337));
|
||||
assertTrue(bobStore.containsPreKey(31337));
|
||||
|
||||
plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(goodMessage));
|
||||
|
||||
assertTrue(originalMessage.equals(new String(plaintext)));
|
||||
assertTrue(!bobPreKeyStore.containsPreKey(31337));
|
||||
assertTrue(!bobStore.containsPreKey(31337));
|
||||
}
|
||||
|
||||
public void testBasicKeyExchange() throws InvalidKeyException, LegacyMessageException, InvalidMessageException, DuplicateMessageException, UntrustedIdentityException, StaleKeyExchangeException, InvalidVersionException, NoSessionException {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
AxolotlStore aliceStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
SessionStore bobSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder bobSessionBuilder = new SessionBuilder(bobSessionStore, bobPreKeyStore,
|
||||
bobSignedPreKeyStore,
|
||||
bobIdentityKeyStore,
|
||||
ALICE_RECIPIENT_ID, 1);
|
||||
AxolotlStore bobStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1);
|
||||
|
||||
KeyExchangeMessage aliceKeyExchangeMessage = aliceSessionBuilder.process();
|
||||
assertTrue(aliceKeyExchangeMessage != null);
|
||||
@ -500,52 +425,35 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
KeyExchangeMessage response = aliceSessionBuilder.process(new KeyExchangeMessage(bobKeyExchangeMessageBytes));
|
||||
|
||||
assertTrue(response == null);
|
||||
assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1));
|
||||
assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1));
|
||||
assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1));
|
||||
assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1));
|
||||
|
||||
runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore,
|
||||
bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore);
|
||||
runInteraction(aliceStore, bobStore);
|
||||
|
||||
aliceSessionStore = new InMemorySessionStore();
|
||||
aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
|
||||
aliceStore = new InMemoryAxolotlStore();
|
||||
aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
aliceKeyExchangeMessage = aliceSessionBuilder.process();
|
||||
|
||||
try {
|
||||
bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage);
|
||||
throw new AssertionError("This identity shouldn't be trusted!");
|
||||
} catch (UntrustedIdentityException uie) {
|
||||
bobIdentityKeyStore.saveIdentity(ALICE_RECIPIENT_ID, aliceKeyExchangeMessage.getIdentityKey());
|
||||
bobStore.saveIdentity(ALICE_RECIPIENT_ID, aliceKeyExchangeMessage.getIdentityKey());
|
||||
bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage);
|
||||
}
|
||||
|
||||
assertTrue(aliceSessionBuilder.process(bobKeyExchangeMessage) == null);
|
||||
|
||||
runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore,
|
||||
bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore);
|
||||
runInteraction(aliceStore, bobStore);
|
||||
}
|
||||
|
||||
public void testSimultaneousKeyExchange()
|
||||
throws InvalidKeyException, DuplicateMessageException, LegacyMessageException, InvalidMessageException, UntrustedIdentityException, StaleKeyExchangeException, NoSessionException {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
AxolotlStore aliceStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
SessionStore bobSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder bobSessionBuilder = new SessionBuilder(bobSessionStore, bobPreKeyStore,
|
||||
bobSignedPreKeyStore,
|
||||
bobIdentityKeyStore,
|
||||
ALICE_RECIPIENT_ID, 1);
|
||||
AxolotlStore bobStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_RECIPIENT_ID, 1);
|
||||
|
||||
KeyExchangeMessage aliceKeyExchange = aliceSessionBuilder.process();
|
||||
KeyExchangeMessage bobKeyExchange = bobSessionBuilder.process();
|
||||
@ -565,44 +473,33 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
assertTrue(aliceAck == null);
|
||||
assertTrue(bobAck == null);
|
||||
|
||||
runInteraction(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore,
|
||||
bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore);
|
||||
runInteraction(aliceStore, bobStore);
|
||||
}
|
||||
|
||||
public void testOptionalOneTimePreKey() throws Exception {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
|
||||
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
|
||||
aliceSignedPreKeyStore,
|
||||
aliceIdentityKeyStore,
|
||||
BOB_RECIPIENT_ID, 1);
|
||||
AxolotlStore aliceStore = new InMemoryAxolotlStore();
|
||||
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
|
||||
SessionStore bobSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
AxolotlStore bobStore = new InMemoryAxolotlStore();
|
||||
|
||||
ECKeyPair bobPreKeyPair = Curve.generateKeyPair();
|
||||
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair();
|
||||
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobIdentityKeyStore.getIdentityKeyPair().getPrivateKey(),
|
||||
byte[] bobSignedPreKeySignature = Curve.calculateSignature(bobStore.getIdentityKeyPair().getPrivateKey(),
|
||||
bobSignedPreKeyPair.getPublicKey().serialize());
|
||||
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobIdentityKeyStore.getLocalRegistrationId(), 1,
|
||||
PreKeyBundle bobPreKey = new PreKeyBundle(bobStore.getLocalRegistrationId(), 1,
|
||||
0, null,
|
||||
22, bobSignedPreKeyPair.getPublicKey(),
|
||||
bobSignedPreKeySignature,
|
||||
bobIdentityKeyStore.getIdentityKeyPair().getPublicKey());
|
||||
bobStore.getIdentityKeyPair().getPublicKey());
|
||||
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
|
||||
assertTrue(aliceSessionStore.containsSession(BOB_RECIPIENT_ID, 1));
|
||||
assertTrue(!aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh());
|
||||
assertTrue(aliceSessionStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
|
||||
assertTrue(aliceStore.containsSession(BOB_RECIPIENT_ID, 1));
|
||||
assertTrue(aliceStore.loadSession(BOB_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
|
||||
|
||||
String originalMessage = "L'homme est condamné à être libre";
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
CiphertextMessage outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
|
||||
assertTrue(outgoingMessage.getType() == CiphertextMessage.PREKEY_TYPE);
|
||||
@ -610,31 +507,24 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
PreKeyWhisperMessage incomingMessage = new PreKeyWhisperMessage(outgoingMessage.serialize());
|
||||
assertTrue(!incomingMessage.getPreKeyId().isPresent());
|
||||
|
||||
bobPreKeyStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobSignedPreKeyStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
bobStore.storePreKey(31337, new PreKeyRecord(bobPreKey.getPreKeyId(), bobPreKeyPair));
|
||||
bobStore.storeSignedPreKey(22, new SignedPreKeyRecord(22, System.currentTimeMillis(), bobSignedPreKeyPair, bobSignedPreKeySignature));
|
||||
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1);
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1);
|
||||
byte[] plaintext = bobSessionCipher.decrypt(incomingMessage);
|
||||
|
||||
assertTrue(bobSessionStore.containsSession(ALICE_RECIPIENT_ID, 1));
|
||||
assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
|
||||
assertTrue(bobSessionStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null);
|
||||
assertTrue(bobStore.containsSession(ALICE_RECIPIENT_ID, 1));
|
||||
assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getSessionVersion() == 3);
|
||||
assertTrue(bobStore.loadSession(ALICE_RECIPIENT_ID, 1).getSessionState().getAliceBaseKey() != null);
|
||||
assertTrue(originalMessage.equals(new String(plaintext)));
|
||||
}
|
||||
|
||||
|
||||
private void runInteraction(SessionStore aliceSessionStore,
|
||||
PreKeyStore alicePreKeyStore,
|
||||
SignedPreKeyStore aliceSignedPreKeyStore,
|
||||
IdentityKeyStore aliceIdentityKeyStore,
|
||||
SessionStore bobSessionStore,
|
||||
PreKeyStore bobPreKeyStore,
|
||||
SignedPreKeyStore bobSignedPreKeyStore,
|
||||
IdentityKeyStore bobIdentityKeyStore)
|
||||
private void runInteraction(AxolotlStore aliceStore, AxolotlStore bobStore)
|
||||
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, NoSessionException
|
||||
{
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1);
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, ALICE_RECIPIENT_ID, 1);
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_RECIPIENT_ID, 1);
|
||||
SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_RECIPIENT_ID, 1);
|
||||
|
||||
String originalMessage = "smert ze smert";
|
||||
CiphertextMessage aliceMessage = aliceSessionCipher.encrypt(originalMessage.getBytes());
|
||||
|
@ -18,6 +18,7 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
||||
import org.whispersystems.libaxolotl.ratchet.AliceAxolotlParameters;
|
||||
import org.whispersystems.libaxolotl.ratchet.BobAxolotlParameters;
|
||||
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
|
||||
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
||||
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyStore;
|
||||
import org.whispersystems.libaxolotl.state.SessionRecord;
|
||||
@ -60,20 +61,14 @@ public class SessionCipherTest extends AndroidTestCase {
|
||||
|
||||
private void runInteraction(SessionRecord aliceSessionRecord, SessionRecord bobSessionRecord)
|
||||
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, NoSuchAlgorithmException, NoSessionException {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
SessionStore bobSessionStore = new InMemorySessionStore();
|
||||
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
|
||||
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
|
||||
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
|
||||
AxolotlStore aliceStore = new InMemoryAxolotlStore();
|
||||
AxolotlStore bobStore = new InMemoryAxolotlStore();
|
||||
|
||||
aliceSessionStore.storeSession(2L, 1, aliceSessionRecord);
|
||||
bobSessionStore.storeSession(3L, 1, bobSessionRecord);
|
||||
aliceStore.storeSession(2L, 1, aliceSessionRecord);
|
||||
bobStore.storeSession(3L, 1, bobSessionRecord);
|
||||
|
||||
SessionCipher aliceCipher = new SessionCipher(aliceSessionStore, alicePreKeyStore, aliceSignedPreKeyStore, aliceIdentityKeyStore, 2L, 1);
|
||||
SessionCipher bobCipher = new SessionCipher(bobSessionStore, bobPreKeyStore, bobSignedPreKeyStore, bobIdentityKeyStore, 3L, 1);
|
||||
SessionCipher aliceCipher = new SessionCipher(aliceStore, 2L, 1);
|
||||
SessionCipher bobCipher = new SessionCipher(bobStore, 3L, 1);
|
||||
|
||||
byte[] alicePlaintext = "This is a plaintext message.".getBytes();
|
||||
CiphertextMessage message = aliceCipher.encrypt(alicePlaintext);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import org.whispersystems.libaxolotl.ratchet.AliceAxolotlParameters;
|
||||
import org.whispersystems.libaxolotl.ratchet.BobAxolotlParameters;
|
||||
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
|
||||
import org.whispersystems.libaxolotl.ratchet.SymmetricAxolotlParameters;
|
||||
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
||||
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyBundle;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyStore;
|
||||
@ -74,6 +75,16 @@ public class SessionBuilder {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SessionBuilder
|
||||
* @param store The {@link org.whispersystems.libaxolotl.state.AxolotlStore} to store all state information in.
|
||||
* @param recipientId The recipient ID of the remote user to build a session with.
|
||||
* @param deviceId The device ID of the remote user's physical device.
|
||||
*/
|
||||
public SessionBuilder(AxolotlStore store, long recipientId, int deviceId) {
|
||||
this(store, store, store, store, recipientId, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new session from a received {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}.
|
||||
*
|
||||
@ -119,7 +130,6 @@ public class SessionBuilder {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
boolean simultaneousInitiate = sessionRecord.getSessionState().hasUnacknowledgedPreKeyMessage();
|
||||
ECKeyPair ourSignedPreKey = signedPreKeyStore.loadSignedPreKey(message.getSignedPreKeyId()).getKeyPair();
|
||||
|
||||
BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder();
|
||||
@ -136,8 +146,7 @@ public class SessionBuilder {
|
||||
parameters.setOurOneTimePreKey(Optional.<ECKeyPair>absent());
|
||||
}
|
||||
|
||||
if (!simultaneousInitiate) sessionRecord.reset();
|
||||
else sessionRecord.archiveCurrentState();
|
||||
if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create());
|
||||
|
||||
@ -145,8 +154,6 @@ public class SessionBuilder {
|
||||
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
|
||||
sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize());
|
||||
|
||||
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
|
||||
|
||||
if (message.getPreKeyId().isPresent() && message.getPreKeyId().get() != Medium.MAX_VALUE) {
|
||||
return message.getPreKeyId();
|
||||
} else {
|
||||
@ -169,7 +176,6 @@ public class SessionBuilder {
|
||||
}
|
||||
|
||||
ECKeyPair ourPreKey = preKeyStore.loadPreKey(message.getPreKeyId().get()).getKeyPair();
|
||||
boolean simultaneousInitiate = sessionRecord.getSessionState().hasUnacknowledgedPreKeyMessage();
|
||||
|
||||
BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder();
|
||||
|
||||
@ -180,15 +186,13 @@ public class SessionBuilder {
|
||||
.setTheirIdentityKey(message.getIdentityKey())
|
||||
.setTheirBaseKey(message.getBaseKey());
|
||||
|
||||
if (!simultaneousInitiate) sessionRecord.reset();
|
||||
else sessionRecord.archiveCurrentState();
|
||||
if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create());
|
||||
|
||||
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
|
||||
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
|
||||
|
||||
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
|
||||
sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize());
|
||||
|
||||
if (message.getPreKeyId().get() != Medium.MAX_VALUE) {
|
||||
return message.getPreKeyId();
|
||||
@ -227,7 +231,6 @@ public class SessionBuilder {
|
||||
}
|
||||
|
||||
boolean supportsV3 = preKey.getSignedPreKey() != null;
|
||||
boolean isExistingSession = sessionStore.containsSession(recipientId, deviceId);
|
||||
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
|
||||
ECKeyPair ourBaseKey = Curve.generateKeyPair();
|
||||
ECPublicKey theirSignedPreKey = supportsV3 ? preKey.getSignedPreKey() : preKey.getPreKey();
|
||||
@ -244,8 +247,7 @@ public class SessionBuilder {
|
||||
.setTheirRatchetKey(theirSignedPreKey)
|
||||
.setTheirOneTimePreKey(supportsV3 ? theirOneTimePreKey : Optional.<ECPublicKey>absent());
|
||||
|
||||
if (isExistingSession) sessionRecord.archiveCurrentState();
|
||||
else sessionRecord.reset();
|
||||
if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
||||
supportsV3 ? 3 : 2,
|
||||
@ -254,6 +256,7 @@ public class SessionBuilder {
|
||||
sessionRecord.getSessionState().setUnacknowledgedPreKeyMessage(theirOneTimePreKeyId, preKey.getSignedPreKeyId(), ourBaseKey.getPublicKey());
|
||||
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
|
||||
sessionRecord.getSessionState().setRemoteRegistrationId(preKey.getRegistrationId());
|
||||
sessionRecord.getSessionState().setAliceBaseKey(ourBaseKey.getPublicKey().serialize());
|
||||
|
||||
sessionStore.storeSession(recipientId, deviceId, sessionRecord);
|
||||
identityKeyStore.saveIdentity(recipientId, preKey.getIdentityKey());
|
||||
@ -316,7 +319,7 @@ public class SessionBuilder {
|
||||
|
||||
SymmetricAxolotlParameters parameters = builder.create();
|
||||
|
||||
sessionRecord.reset();
|
||||
if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
||||
Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION),
|
||||
@ -358,7 +361,7 @@ public class SessionBuilder {
|
||||
.setTheirRatchetKey(message.getRatchetKey())
|
||||
.setTheirIdentityKey(message.getIdentityKey());
|
||||
|
||||
sessionRecord.reset();
|
||||
if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
||||
Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION),
|
||||
|
@ -25,6 +25,7 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
||||
import org.whispersystems.libaxolotl.ratchet.ChainKey;
|
||||
import org.whispersystems.libaxolotl.ratchet.MessageKeys;
|
||||
import org.whispersystems.libaxolotl.ratchet.RootKey;
|
||||
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
||||
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyStore;
|
||||
import org.whispersystems.libaxolotl.state.SessionRecord;
|
||||
@ -37,6 +38,7 @@ import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@ -89,6 +91,10 @@ public class SessionCipher {
|
||||
identityKeyStore, recipientId, deviceId);
|
||||
}
|
||||
|
||||
public SessionCipher(AxolotlStore store, long recipientId, int deviceId) {
|
||||
this(store, store, store, store, recipientId, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a message.
|
||||
*
|
||||
@ -198,19 +204,28 @@ public class SessionCipher {
|
||||
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException
|
||||
{
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionState sessionState = sessionRecord.getSessionState();
|
||||
List<SessionState> previousStates = sessionRecord.getPreviousSessionStates();
|
||||
Iterator<SessionState> previousStates = sessionRecord.getPreviousSessionStates().iterator();
|
||||
List<Exception> exceptions = new LinkedList<>();
|
||||
|
||||
try {
|
||||
return decrypt(sessionState, ciphertext);
|
||||
SessionState sessionState = new SessionState(sessionRecord.getSessionState());
|
||||
byte[] plaintext = decrypt(sessionState, ciphertext);
|
||||
|
||||
sessionRecord.setState(sessionState);
|
||||
return plaintext;
|
||||
} catch (InvalidMessageException e) {
|
||||
exceptions.add(e);
|
||||
}
|
||||
|
||||
for (SessionState previousState : previousStates) {
|
||||
while (previousStates.hasNext()) {
|
||||
try {
|
||||
return decrypt(previousState, ciphertext);
|
||||
SessionState promotedState = new SessionState(previousStates.next());
|
||||
byte[] plaintext = decrypt(promotedState, ciphertext);
|
||||
|
||||
previousStates.remove();
|
||||
sessionRecord.promoteState(promotedState);
|
||||
|
||||
return plaintext;
|
||||
} catch (InvalidMessageException e) {
|
||||
exceptions.add(e);
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
package org.whispersystems.libaxolotl.state;
|
||||
|
||||
public interface AxolotlStore
|
||||
extends IdentityKeyStore, PreKeyStore, SessionStore, SignedPreKeyStore
|
||||
{
|
||||
}
|
@ -15,18 +15,25 @@ import static org.whispersystems.libaxolotl.state.StorageProtos.SessionStructure
|
||||
*/
|
||||
public class SessionRecord {
|
||||
|
||||
private SessionState sessionState = new SessionState();
|
||||
private List<SessionState> previousStates = new LinkedList<>();
|
||||
private static final int ARCHIVED_STATES_MAX_LENGTH = 40;
|
||||
|
||||
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) {
|
||||
this.sessionState = sessionState;
|
||||
this.fresh = false;
|
||||
}
|
||||
|
||||
public SessionRecord(byte[] serialized) throws IOException {
|
||||
RecordStructure record = RecordStructure.parseFrom(serialized);
|
||||
this.sessionState = new SessionState(record.getCurrentSession());
|
||||
this.fresh = false;
|
||||
|
||||
for (SessionStructure previousStructure : record.getPreviousSessionsList()) {
|
||||
previousStates.add(new SessionState(previousStructure));
|
||||
@ -62,14 +69,9 @@ public class SessionRecord {
|
||||
return previousStates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the current SessionRecord, clearing all "previous" session states,
|
||||
* and resetting the current {@link org.whispersystems.libaxolotl.state.SessionState}
|
||||
* to a fresh state.
|
||||
*/
|
||||
public void reset() {
|
||||
this.sessionState = new SessionState();
|
||||
this.previousStates = new LinkedList<>();
|
||||
|
||||
public boolean isFresh() {
|
||||
return fresh;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,8 +80,20 @@ public class SessionRecord {
|
||||
* with a fresh reset instance.
|
||||
*/
|
||||
public void archiveCurrentState() {
|
||||
this.previousStates.add(sessionState);
|
||||
this.sessionState = new SessionState();
|
||||
promoteState(new SessionState());
|
||||
}
|
||||
|
||||
public void promoteState(SessionState promotedState) {
|
||||
this.previousStates.addFirst(sessionState);
|
||||
this.sessionState = promotedState;
|
||||
|
||||
if (previousStates.size() > ARCHIVED_STATES_MAX_LENGTH) {
|
||||
previousStates.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
public void setState(SessionState sessionState) {
|
||||
this.sessionState = sessionState;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,6 +59,10 @@ public class SessionState {
|
||||
this.sessionStructure = sessionStructure;
|
||||
}
|
||||
|
||||
public SessionState(SessionState copy) {
|
||||
this.sessionStructure = copy.sessionStructure.toBuilder().build();
|
||||
}
|
||||
|
||||
public SessionStructure getStructure() {
|
||||
return sessionStructure;
|
||||
}
|
||||
@ -73,16 +77,6 @@ public class SessionState {
|
||||
.build();
|
||||
}
|
||||
|
||||
public void setNeedsRefresh(boolean needsRefresh) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setNeedsRefresh(needsRefresh)
|
||||
.build();
|
||||
}
|
||||
|
||||
public boolean getNeedsRefresh() {
|
||||
return this.sessionStructure.getNeedsRefresh();
|
||||
}
|
||||
|
||||
public void setSessionVersion(int version) {
|
||||
this.sessionStructure = this.sessionStructure.toBuilder()
|
||||
.setSessionVersion(version)
|
||||
|
@ -35,9 +35,7 @@ public class SessionUtil {
|
||||
int deviceId = recipientDevice.getDeviceId();
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
|
||||
return
|
||||
sessionStore.containsSession(recipientId, deviceId) &&
|
||||
!sessionStore.loadSession(recipientId, deviceId).getSessionState().getNeedsRefresh();
|
||||
return sessionStore.containsSession(recipientId, deviceId);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user