mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Javadocs, and some minor refactoring.
This commit is contained in:
parent
af45e5d544
commit
5a3c19fe3e
@ -16,7 +16,7 @@ public class InMemorySessionStore implements SessionStore {
|
||||
public InMemorySessionStore() {}
|
||||
|
||||
@Override
|
||||
public synchronized SessionRecord get(long recipientId, int deviceId) {
|
||||
public synchronized SessionRecord load(long recipientId, int deviceId) {
|
||||
if (contains(recipientId, deviceId)) {
|
||||
return new InMemorySessionRecord(sessions.get(new Pair<>(recipientId, deviceId)));
|
||||
} else {
|
||||
@ -38,7 +38,7 @@ public class InMemorySessionStore implements SessionStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void put(long recipientId, int deviceId, SessionRecord record) {
|
||||
public synchronized void store(long recipientId, int deviceId, SessionRecord record) {
|
||||
sessions.put(new Pair<>(recipientId, deviceId), record);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.whispersystems.test;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.libaxolotl.DuplicateMessageException;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
@ -57,7 +56,7 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
aliceSessionBuilder.process(bobPreKey);
|
||||
|
||||
assertTrue(aliceSessionStore.contains(BOB_RECIPIENT_ID, 1));
|
||||
assertTrue(!aliceSessionStore.get(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh());
|
||||
assertTrue(!aliceSessionStore.load(BOB_RECIPIENT_ID, 1).getSessionState().getNeedsRefresh());
|
||||
|
||||
String originalMessage = "L'homme est condamné à être libre";
|
||||
SessionCipher aliceSessionCipher = new SessionCipher(aliceSessionStore, BOB_RECIPIENT_ID, 1);
|
||||
@ -95,15 +94,11 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
KeyExchangeMessage aliceKeyExchangeMessage = aliceSessionBuilder.process();
|
||||
KeyExchangeMessage bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage);
|
||||
|
||||
Log.w("SessionBuilderTest", "Record from test: " + bobSessionStore.get(ALICE_RECIPIENT_ID, 1));
|
||||
|
||||
assertTrue(bobKeyExchangeMessage != null);
|
||||
assertTrue(aliceKeyExchangeMessage != null);
|
||||
|
||||
KeyExchangeMessage response = aliceSessionBuilder.process(bobKeyExchangeMessage);
|
||||
|
||||
Log.w("SessionBuilderTest", "Record from test 2: " + bobSessionStore.get(ALICE_RECIPIENT_ID, 1));
|
||||
|
||||
assertTrue(response == null);
|
||||
assertTrue(aliceSessionStore.contains(BOB_RECIPIENT_ID, 1));
|
||||
assertTrue(bobSessionStore.contains(ALICE_RECIPIENT_ID, 1));
|
||||
@ -171,7 +166,9 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
assertTrue(new String(plaintext).equals(originalMessage));
|
||||
|
||||
for (int i=0;i<10;i++) {
|
||||
String loopingMessage = ("You can only desire based on what you know: " + i);
|
||||
String loopingMessage = ("What do we mean by saying that existence precedes essence? " +
|
||||
"We mean that man first of all exists, encounters himself, " +
|
||||
"surges up in the world--and defines himself aftward. " + i);
|
||||
CiphertextMessage aliceLoopingMessage = aliceSessionCipher.encrypt(loopingMessage.getBytes());
|
||||
|
||||
byte[] loopingPlaintext = bobSessionCipher.decrypt(aliceLoopingMessage.serialize());
|
||||
@ -179,7 +176,9 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
}
|
||||
|
||||
for (int i=0;i<10;i++) {
|
||||
String loopingMessage = ("You can only desire based on what you know: " + i);
|
||||
String loopingMessage = ("What do we mean by saying that existence precedes essence? " +
|
||||
"We mean that man first of all exists, encounters himself, " +
|
||||
"surges up in the world--and defines himself aftward. " + i);
|
||||
CiphertextMessage bobLoopingMessage = bobSessionCipher.encrypt(loopingMessage.getBytes());
|
||||
|
||||
byte[] loopingPlaintext = aliceSessionCipher.decrypt(bobLoopingMessage.serialize());
|
||||
@ -189,14 +188,18 @@ public class SessionBuilderTest extends AndroidTestCase {
|
||||
Set<Pair<String, CiphertextMessage>> aliceOutOfOrderMessages = new HashSet<>();
|
||||
|
||||
for (int i=0;i<10;i++) {
|
||||
String loopingMessage = ("You can only desire based on what you know: " + i);
|
||||
String loopingMessage = ("What do we mean by saying that existence precedes essence? " +
|
||||
"We mean that man first of all exists, encounters himself, " +
|
||||
"surges up in the world--and defines himself aftward. " + i);
|
||||
CiphertextMessage aliceLoopingMessage = aliceSessionCipher.encrypt(loopingMessage.getBytes());
|
||||
|
||||
aliceOutOfOrderMessages.add(new Pair<>(loopingMessage, aliceLoopingMessage));
|
||||
}
|
||||
|
||||
for (int i=0;i<10;i++) {
|
||||
String loopingMessage = ("You can only desire based on what you know: " + i);
|
||||
String loopingMessage = ("What do we mean by saying that existence precedes essence? " +
|
||||
"We mean that man first of all exists, encounters himself, " +
|
||||
"surges up in the world--and defines himself aftward. " + i);
|
||||
CiphertextMessage aliceLoopingMessage = aliceSessionCipher.encrypt(loopingMessage.getBytes());
|
||||
|
||||
byte[] loopingPlaintext = bobSessionCipher.decrypt(aliceLoopingMessage.serialize());
|
||||
|
@ -33,8 +33,8 @@ public class SessionCipherTest extends AndroidTestCase {
|
||||
SessionStore aliceSessionStore = new InMemorySessionStore();
|
||||
SessionStore bobSessionStore = new InMemorySessionStore();
|
||||
|
||||
aliceSessionStore.put(2L, 1, aliceSessionRecord);
|
||||
bobSessionStore.put(3L, 1, bobSessionRecord);
|
||||
aliceSessionStore.store(2L, 1, aliceSessionRecord);
|
||||
bobSessionStore.store(3L, 1, bobSessionRecord);
|
||||
|
||||
SessionCipher aliceCipher = new SessionCipher(aliceSessionStore, 2L, 1);
|
||||
SessionCipher bobCipher = new SessionCipher(bobSessionStore, 3L, 1);
|
||||
|
@ -14,9 +14,26 @@ import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyStore;
|
||||
import org.whispersystems.libaxolotl.state.SessionRecord;
|
||||
import org.whispersystems.libaxolotl.state.SessionStore;
|
||||
import org.whispersystems.libaxolotl.util.Helper;
|
||||
import org.whispersystems.libaxolotl.util.KeyHelper;
|
||||
import org.whispersystems.libaxolotl.util.Medium;
|
||||
|
||||
/**
|
||||
* SessionBuilder is responsible for setting up encrypted sessions.
|
||||
* Once a session has been established, {@link org.whispersystems.libaxolotl.SessionCipher}
|
||||
* can be used to encrypt/decrypt messages in that session.
|
||||
* <p>
|
||||
* Sessions are built from one of three different possible vectors:
|
||||
* <ol>
|
||||
* <li>A {@link org.whispersystems.libaxolotl.state.PreKey} retrieved from a server.</li>
|
||||
* <li>A {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage} received from a client.</li>
|
||||
* <li>A {@link org.whispersystems.libaxolotl.protocol.KeyExchangeMessage} sent to or received from a client.</li>
|
||||
* </ol>
|
||||
*
|
||||
* Sessions are constructed per recipientId + deviceId tuple. Remote logical users are identified
|
||||
* by their recipientId, and each logical recipientId can have multiple physical devices.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SessionBuilder {
|
||||
|
||||
private static final String TAG = SessionBuilder.class.getSimpleName();
|
||||
@ -27,6 +44,15 @@ public class SessionBuilder {
|
||||
private final long recipientId;
|
||||
private final int deviceId;
|
||||
|
||||
/**
|
||||
* Constructs a SessionBuilder.
|
||||
*
|
||||
* @param sessionStore The {@link org.whispersystems.libaxolotl.state.SessionStore} to store the constructed session in.
|
||||
* @param preKeyStore The {@link org.whispersystems.libaxolotl.state.PreKeyStore} where the client's local {@link org.whispersystems.libaxolotl.state.PreKeyRecord}s are stored.
|
||||
* @param identityKeyStore The {@link org.whispersystems.libaxolotl.state.IdentityKeyStore} containing the client's identity key information.
|
||||
* @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(SessionStore sessionStore,
|
||||
PreKeyStore preKeyStore,
|
||||
IdentityKeyStore identityKeyStore,
|
||||
@ -39,6 +65,19 @@ public class SessionBuilder {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new session from a received {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}.
|
||||
*
|
||||
* After a session is constructed in this way, the embedded {@link org.whispersystems.libaxolotl.protocol.WhisperMessage}
|
||||
* can be decrypted.
|
||||
*
|
||||
* @param message The received {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}.
|
||||
* @throws org.whispersystems.libaxolotl.InvalidKeyIdException when there is no local
|
||||
* {@link org.whispersystems.libaxolotl.state.PreKeyRecord}
|
||||
* that corresponds to the PreKey ID in
|
||||
* the message.
|
||||
* @throws org.whispersystems.libaxolotl.InvalidKeyException when the message is formatted incorrectly.
|
||||
*/
|
||||
public void process(PreKeyWhisperMessage message)
|
||||
throws InvalidKeyIdException, InvalidKeyException
|
||||
{
|
||||
@ -59,7 +98,7 @@ public class SessionBuilder {
|
||||
if (!preKeyStore.contains(preKeyId))
|
||||
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
|
||||
|
||||
SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
|
||||
SessionRecord sessionRecord = sessionStore.load(recipientId, deviceId);
|
||||
PreKeyRecord preKeyRecord = preKeyStore.load(preKeyId);
|
||||
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
|
||||
ECKeyPair ourEphemeralKey = ourBaseKey;
|
||||
@ -79,7 +118,7 @@ public class SessionBuilder {
|
||||
|
||||
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
|
||||
|
||||
sessionStore.put(recipientId, deviceId, sessionRecord);
|
||||
sessionStore.store(recipientId, deviceId, sessionRecord);
|
||||
|
||||
if (preKeyId != Medium.MAX_VALUE) {
|
||||
preKeyStore.remove(preKeyId);
|
||||
@ -88,8 +127,16 @@ public class SessionBuilder {
|
||||
identityKeyStore.saveIdentity(recipientId, theirIdentityKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new session from a {@link org.whispersystems.libaxolotl.state.PreKey} retrieved from
|
||||
* a server.
|
||||
*
|
||||
* @param preKey A PreKey for the destination recipient, retrieved from a server.
|
||||
* @throws InvalidKeyException when the {@link org.whispersystems.libaxolotl.state.PreKey} is
|
||||
* badly formatted.
|
||||
*/
|
||||
public void process(PreKey preKey) throws InvalidKeyException {
|
||||
SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
|
||||
SessionRecord sessionRecord = sessionStore.load(recipientId, deviceId);
|
||||
ECKeyPair ourBaseKey = Curve.generateKeyPair(true);
|
||||
ECKeyPair ourEphemeralKey = Curve.generateKeyPair(true);
|
||||
ECPublicKey theirBaseKey = preKey.getPublicKey();
|
||||
@ -108,14 +155,22 @@ public class SessionBuilder {
|
||||
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
|
||||
sessionRecord.getSessionState().setRemoteRegistrationId(preKey.getRegistrationId());
|
||||
|
||||
sessionStore.put(recipientId, deviceId, sessionRecord);
|
||||
sessionStore.store(recipientId, deviceId, sessionRecord);
|
||||
|
||||
identityKeyStore.saveIdentity(recipientId, preKey.getIdentityKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new session from a {@link org.whispersystems.libaxolotl.protocol.KeyExchangeMessage}
|
||||
* received from a remote client.
|
||||
*
|
||||
* @param message The received KeyExchangeMessage.
|
||||
* @return The KeyExchangeMessage to respond with, or null if no response is necessary.
|
||||
* @throws InvalidKeyException if the received KeyExchangeMessage is badly formatted.
|
||||
*/
|
||||
public KeyExchangeMessage process(KeyExchangeMessage message) throws InvalidKeyException {
|
||||
KeyExchangeMessage responseMessage = null;
|
||||
SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
|
||||
SessionRecord sessionRecord = sessionStore.load(recipientId, deviceId);
|
||||
|
||||
Log.w(TAG, "Received key exchange with sequence: " + message.getSequence());
|
||||
|
||||
@ -170,23 +225,28 @@ public class SessionBuilder {
|
||||
ourIdentityKey, message.getIdentityKey());
|
||||
|
||||
sessionRecord.getSessionState().setSessionVersion(message.getVersion());
|
||||
sessionStore.put(recipientId, deviceId, sessionRecord);
|
||||
sessionStore.store(recipientId, deviceId, sessionRecord);
|
||||
|
||||
identityKeyStore.saveIdentity(recipientId, message.getIdentityKey());
|
||||
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate a new session by sending an initial KeyExchangeMessage to the recipient.
|
||||
*
|
||||
* @return the KeyExchangeMessage to deliver.
|
||||
*/
|
||||
public KeyExchangeMessage process() {
|
||||
int sequence = Helper.getRandomSequence(65534) + 1;
|
||||
int sequence = KeyHelper.getRandomSequence(65534) + 1;
|
||||
int flags = KeyExchangeMessage.INITIATE_FLAG;
|
||||
ECKeyPair baseKey = Curve.generateKeyPair(true);
|
||||
ECKeyPair ephemeralKey = Curve.generateKeyPair(true);
|
||||
IdentityKeyPair identityKey = identityKeyStore.getIdentityKeyPair();
|
||||
SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
|
||||
SessionRecord sessionRecord = sessionStore.load(recipientId, deviceId);
|
||||
|
||||
sessionRecord.getSessionState().setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
|
||||
sessionStore.put(recipientId, deviceId, sessionRecord);
|
||||
sessionStore.store(recipientId, deviceId, sessionRecord);
|
||||
|
||||
return new KeyExchangeMessage(sequence, flags,
|
||||
baseKey.getPublicKey(),
|
||||
|
@ -43,6 +43,15 @@ import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* The main entry point for Axolotl encrypt/decrypt operations.
|
||||
*
|
||||
* Once a session has been established with {@link SessionBuilder},
|
||||
* this class can be used for all encrypt/decrypt operations within
|
||||
* that session.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class SessionCipher {
|
||||
|
||||
private static final Object SESSION_LOCK = new Object();
|
||||
@ -51,15 +60,30 @@ public class SessionCipher {
|
||||
private final long recipientId;
|
||||
private final int deviceId;
|
||||
|
||||
/**
|
||||
* Construct a SessionCipher for encrypt/decrypt operations on a session.
|
||||
* In order to use SessionCipher, a session must have already been created
|
||||
* and stored using {@link SessionBuilder}.
|
||||
*
|
||||
* @param sessionStore The {@link SessionStore} that contains a session for this recipient.
|
||||
* @param recipientId The remote ID that messages will be encrypted to or decrypted from.
|
||||
* @param deviceId The device corresponding to the recipientId.
|
||||
*/
|
||||
public SessionCipher(SessionStore sessionStore, long recipientId, int deviceId) {
|
||||
this.sessionStore = sessionStore;
|
||||
this.recipientId = recipientId;
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a message.
|
||||
*
|
||||
* @param paddedMessage The plaintext message bytes, optionally padded to a constant multiple.
|
||||
* @return A ciphertext message encrypted to the recipient+device tuple.
|
||||
*/
|
||||
public CiphertextMessage encrypt(byte[] paddedMessage) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
|
||||
SessionRecord sessionRecord = sessionStore.load(recipientId, deviceId);
|
||||
SessionState sessionState = sessionRecord.getSessionState();
|
||||
ChainKey chainKey = sessionState.getSenderChainKey();
|
||||
MessageKeys messageKeys = chainKey.getMessageKeys();
|
||||
@ -82,23 +106,34 @@ public class SessionCipher {
|
||||
}
|
||||
|
||||
sessionState.setSenderChainKey(chainKey.getNextChainKey());
|
||||
sessionStore.put(recipientId, deviceId, sessionRecord);
|
||||
sessionStore.store(recipientId, deviceId, sessionRecord);
|
||||
return ciphertextMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] decodedMessage)
|
||||
/**
|
||||
* Decrypt a message.
|
||||
*
|
||||
* @param ciphertext The ciphertext message bytes corresponding to a serialized
|
||||
* {@link WhisperMessage}.
|
||||
* @return The plaintext.
|
||||
* @throws InvalidMessageException if the input is not valid ciphertext.
|
||||
* @throws DuplicateMessageException if the input is a message that has already been received.
|
||||
* @throws LegacyMessageException if the input is a message formatted by a protocol version that
|
||||
* is no longer supported.
|
||||
*/
|
||||
public byte[] decrypt(byte[] ciphertext)
|
||||
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
||||
{
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecord sessionRecord = sessionStore.get(recipientId, deviceId);
|
||||
SessionRecord sessionRecord = sessionStore.load(recipientId, deviceId);
|
||||
SessionState sessionState = sessionRecord.getSessionState();
|
||||
List<SessionState> previousStates = sessionRecord.getPreviousSessionStates();
|
||||
List<Exception> exceptions = new LinkedList<>();
|
||||
|
||||
try {
|
||||
byte[] plaintext = decrypt(sessionState, decodedMessage);
|
||||
sessionStore.put(recipientId, deviceId, sessionRecord);
|
||||
byte[] plaintext = decrypt(sessionState, ciphertext);
|
||||
sessionStore.store(recipientId, deviceId, sessionRecord);
|
||||
|
||||
return plaintext;
|
||||
} catch (InvalidMessageException e) {
|
||||
@ -107,8 +142,8 @@ public class SessionCipher {
|
||||
|
||||
for (SessionState previousState : previousStates) {
|
||||
try {
|
||||
byte[] plaintext = decrypt(previousState, decodedMessage);
|
||||
sessionStore.put(recipientId, deviceId, sessionRecord);
|
||||
byte[] plaintext = decrypt(previousState, ciphertext);
|
||||
sessionStore.store(recipientId, deviceId, sessionRecord);
|
||||
|
||||
return plaintext;
|
||||
} catch (InvalidMessageException e) {
|
||||
@ -120,7 +155,7 @@ public class SessionCipher {
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
|
||||
private byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
|
||||
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
||||
{
|
||||
if (!sessionState.hasSenderChain()) {
|
||||
@ -146,7 +181,7 @@ public class SessionCipher {
|
||||
|
||||
public int getRemoteRegistrationId() {
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecord record = sessionStore.get(recipientId, deviceId);
|
||||
SessionRecord record = sessionStore.load(recipientId, deviceId);
|
||||
return record.getSessionState().getRemoteRegistrationId();
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,38 @@ package org.whispersystems.libaxolotl.state;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
|
||||
/**
|
||||
* Provides an interface to identity information.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public interface IdentityKeyStore {
|
||||
|
||||
/**
|
||||
* Get the local client's identity key pair.
|
||||
*
|
||||
* @return The local client's persistent identity key pair.
|
||||
*/
|
||||
public IdentityKeyPair getIdentityKeyPair();
|
||||
|
||||
/**
|
||||
* Return the local client's registration ID.
|
||||
* <p>
|
||||
* Clients should maintain a registration ID, a random number
|
||||
* between 1 and 16380 that's generated once at install time.
|
||||
*
|
||||
* @return the local client's registration ID.
|
||||
*/
|
||||
public int getLocalRegistrationId();
|
||||
|
||||
/**
|
||||
* Save a remote client's identity key
|
||||
* <p>
|
||||
* Store a remote client's identity key as trusted.
|
||||
*
|
||||
* @param recipientId The recipient ID of the remote client.
|
||||
* @param identityKey The remote client's identity key.
|
||||
*/
|
||||
public void saveIdentity(long recipientId, IdentityKey identityKey);
|
||||
|
||||
}
|
||||
|
@ -3,10 +3,34 @@ package org.whispersystems.libaxolotl.state;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
|
||||
/**
|
||||
* An interface that describes a remote PreKey.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public interface PreKey {
|
||||
/**
|
||||
* @return the device ID this PreKey belongs to.
|
||||
*/
|
||||
public int getDeviceId();
|
||||
|
||||
/**
|
||||
* @return the unique key ID for this PreKey.
|
||||
*/
|
||||
public int getKeyId();
|
||||
|
||||
/**
|
||||
* @return the public key for this PreKey.
|
||||
*/
|
||||
public ECPublicKey getPublicKey();
|
||||
|
||||
/**
|
||||
* @return the {@link org.whispersystems.libaxolotl.IdentityKey} of this PreKeys owner.
|
||||
*/
|
||||
public IdentityKey getIdentityKey();
|
||||
|
||||
/**
|
||||
* @return the registration ID associated with this PreKey.
|
||||
*/
|
||||
public int getRegistrationId();
|
||||
}
|
||||
|
@ -2,8 +2,24 @@ package org.whispersystems.libaxolotl.state;
|
||||
|
||||
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||
|
||||
/**
|
||||
* An interface describing a locally stored PreKey.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public interface PreKeyRecord {
|
||||
/**
|
||||
* @return the PreKey's ID.
|
||||
*/
|
||||
public int getId();
|
||||
|
||||
/**
|
||||
* @return the PreKey's key pair.
|
||||
*/
|
||||
public ECKeyPair getKeyPair();
|
||||
|
||||
/**
|
||||
* @return a serialized version of this PreKey.
|
||||
*/
|
||||
public byte[] serialize();
|
||||
}
|
||||
|
@ -2,11 +2,41 @@ package org.whispersystems.libaxolotl.state;
|
||||
|
||||
import org.whispersystems.libaxolotl.InvalidKeyIdException;
|
||||
|
||||
/**
|
||||
* An interface describing the local storage of {@link PreKeyRecord}s.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public interface PreKeyStore {
|
||||
|
||||
/**
|
||||
* Load a local PreKeyRecord.
|
||||
*
|
||||
* @param preKeyId the ID of the local PreKeyRecord.
|
||||
* @return the corresponding PreKeyRecord.
|
||||
* @throws InvalidKeyIdException when there is no corresponding PreKeyRecord.
|
||||
*/
|
||||
public PreKeyRecord load(int preKeyId) throws InvalidKeyIdException;
|
||||
|
||||
/**
|
||||
* Store a local PreKeyRecord.
|
||||
*
|
||||
* @param preKeyId the ID of the PreKeyRecord to store.
|
||||
* @param record the PreKeyRecord.
|
||||
*/
|
||||
public void store(int preKeyId, PreKeyRecord record);
|
||||
|
||||
/**
|
||||
* @param preKeyId A PreKeyRecord ID.
|
||||
* @return true if the store has a record for the preKeyId, otherwise false.
|
||||
*/
|
||||
public boolean contains(int preKeyId);
|
||||
|
||||
/**
|
||||
* Delete a PreKeyRecord from local storage.
|
||||
*
|
||||
* @param preKeyId The ID of the PreKeyRecord to remove.
|
||||
*/
|
||||
public void remove(int preKeyId);
|
||||
|
||||
}
|
||||
|
@ -2,12 +2,44 @@ package org.whispersystems.libaxolotl.state;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A SessionRecord encapsulates the state of an ongoing session.
|
||||
* <p>
|
||||
* It contains the current {@link org.whispersystems.libaxolotl.state.SessionState},
|
||||
* in addition to previous {@link SessionState}s for the same recipient, which need
|
||||
* to be maintained in some situations.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public interface SessionRecord {
|
||||
|
||||
/**
|
||||
* @return the current {@link org.whispersystems.libaxolotl.state.SessionState}
|
||||
*/
|
||||
public SessionState getSessionState();
|
||||
|
||||
/**
|
||||
* @return the list of all currently maintained "previous" session states.
|
||||
*/
|
||||
public List<SessionState> getPreviousSessionStates();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Move the current {@link SessionState} into the list of "previous" session states,
|
||||
* and replace the current {@link org.whispersystems.libaxolotl.state.SessionState}
|
||||
* with a fresh reset instance.
|
||||
*/
|
||||
public void archiveCurrentState();
|
||||
|
||||
/**
|
||||
* @return a serialized version of the current SessionRecord.
|
||||
*/
|
||||
public byte[] serialize();
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,11 @@ import org.whispersystems.libaxolotl.ratchet.MessageKeys;
|
||||
import org.whispersystems.libaxolotl.ratchet.RootKey;
|
||||
import org.whispersystems.libaxolotl.util.Pair;
|
||||
|
||||
/**
|
||||
* The current session state.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public interface SessionState {
|
||||
public void setNeedsRefresh(boolean needsRefresh);
|
||||
public boolean getNeedsRefresh();
|
||||
|
@ -2,13 +2,67 @@ package org.whispersystems.libaxolotl.state;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The interface to the durable store of session state information
|
||||
* for remote clients.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public interface SessionStore {
|
||||
|
||||
public SessionRecord get(long recipientId, int deviceId);
|
||||
/**
|
||||
* Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple,
|
||||
* or a new SessionRecord if one does not currently exist.
|
||||
* <p>
|
||||
* It is important that implementations return a copy of the current durable information. The
|
||||
* returned SessionRecord may be modified, but those changes should not have an effect on the
|
||||
* durable session state (what is returned by subsequent calls to this method) without the
|
||||
* store method being called here first.
|
||||
*
|
||||
* @param recipientId The recipientID of the remote client.
|
||||
* @param deviceId The deviceID of the remote client.
|
||||
* @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or
|
||||
* a new SessionRecord if one does not currently exist.
|
||||
*/
|
||||
public SessionRecord load(long recipientId, int deviceId);
|
||||
|
||||
/**
|
||||
* Returns all known devices with active sessions for a recipient
|
||||
*
|
||||
* @param recipientId the recipient ID.
|
||||
* @return all known sub-devices with active sessions.
|
||||
*/
|
||||
public List<Integer> getSubDeviceSessions(long recipientId);
|
||||
public void put(long recipientId, int deviceId, SessionRecord record);
|
||||
|
||||
/**
|
||||
* Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
|
||||
* @param recipientId the recipient ID of the remote client.
|
||||
* @param deviceId the device ID of the remote client.
|
||||
* @param record the current SessionRecord for the remote client.
|
||||
*/
|
||||
public void store(long recipientId, int deviceId, SessionRecord record);
|
||||
|
||||
/**
|
||||
* Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple.
|
||||
* @param recipientId the recipient ID of the remote client.
|
||||
* @param deviceId the device ID of the remote client.
|
||||
* @return true if a {@link SessionRecord} exists, false otherwise.
|
||||
*/
|
||||
public boolean contains(long recipientId, int deviceId);
|
||||
|
||||
/**
|
||||
* Remove a {@link SessionRecord} for a recipientId + deviceId tuple.
|
||||
*
|
||||
* @param recipientId the recipient ID of the remote client.
|
||||
* @param deviceId the device ID of the remote client.
|
||||
*/
|
||||
public void delete(long recipientId, int deviceId);
|
||||
|
||||
/**
|
||||
* Remove the {@link SessionRecord}s corresponding to all devices of a recipientId.
|
||||
*
|
||||
* @param recipientId the recipient ID of the remote client.
|
||||
*/
|
||||
public void deleteAll(long recipientId);
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package org.whispersystems.libaxolotl.util;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class Helper {
|
||||
public class KeyHelper {
|
||||
|
||||
public static int getRandomSequence(int max) {
|
||||
try {
|
@ -26,7 +26,7 @@ public class SessionUtil {
|
||||
|
||||
return
|
||||
sessionStore.contains(recipientId, deviceId) &&
|
||||
!sessionStore.get(recipientId, deviceId).getSessionState().getNeedsRefresh();
|
||||
!sessionStore.load(recipientId, deviceId).getSessionState().getNeedsRefresh();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ public class TextSecureSessionStore implements SessionStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionRecord get(long recipientId, int deviceId) {
|
||||
public SessionRecord load(long recipientId, int deviceId) {
|
||||
synchronized (FILE_LOCK) {
|
||||
try {
|
||||
FileInputStream input = new FileInputStream(getSessionFile(recipientId, deviceId));
|
||||
@ -45,7 +45,7 @@ public class TextSecureSessionStore implements SessionStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(long recipientId, int deviceId, SessionRecord record) {
|
||||
public void store(long recipientId, int deviceId, SessionRecord record) {
|
||||
try {
|
||||
RandomAccessFile sessionFile = new RandomAccessFile(getSessionFile(recipientId, deviceId), "rw");
|
||||
FileChannel out = sessionFile.getChannel();
|
||||
@ -63,7 +63,7 @@ public class TextSecureSessionStore implements SessionStore {
|
||||
@Override
|
||||
public boolean contains(long recipientId, int deviceId) {
|
||||
return getSessionFile(recipientId, deviceId).exists() &&
|
||||
get(recipientId, deviceId).getSessionState().hasSenderChain();
|
||||
load(recipientId, deviceId).getSessionState().hasSenderChain();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -183,8 +183,8 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
|
||||
|
||||
private IdentityKey getRemoteIdentityKey(MasterSecret masterSecret, Recipient recipient) {
|
||||
SessionStore sessionStore = new TextSecureSessionStore(this, masterSecret);
|
||||
SessionRecord record = sessionStore.get(recipient.getRecipientId(),
|
||||
RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
SessionRecord record = sessionStore.load(recipient.getRecipientId(),
|
||||
RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
|
||||
if (record == null) {
|
||||
return null;
|
||||
|
@ -79,7 +79,7 @@ public class KeyExchangeInitiator {
|
||||
Recipient recipient)
|
||||
{
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
SessionRecord sessionRecord = sessionStore.get(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
SessionRecord sessionRecord = sessionStore.load(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
|
||||
return sessionRecord.getSessionState().hasPendingPreKey();
|
||||
}
|
||||
|
@ -77,8 +77,8 @@ public class KeyExchangeProcessor {
|
||||
}
|
||||
|
||||
public boolean isStale(KeyExchangeMessage message) {
|
||||
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
|
||||
recipientDevice.getDeviceId());
|
||||
SessionRecord sessionRecord = sessionStore.load(recipientDevice.getRecipientId(),
|
||||
recipientDevice.getDeviceId());
|
||||
|
||||
return
|
||||
message.isResponse() &&
|
||||
|
Loading…
Reference in New Issue
Block a user