From 5a3c19fe3eb62050443f21a1ffbbea8ce92c15c7 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 24 Apr 2014 12:28:38 -0700 Subject: [PATCH] Javadocs, and some minor refactoring. --- .../test/InMemorySessionStore.java | 4 +- .../test/SessionBuilderTest.java | 23 +++--- .../test/SessionCipherTest.java | 4 +- .../libaxolotl/SessionBuilder.java | 80 ++++++++++++++++--- .../libaxolotl/SessionCipher.java | 55 ++++++++++--- .../libaxolotl/state/IdentityKeyStore.java | 28 +++++++ .../libaxolotl/state/PreKey.java | 24 ++++++ .../libaxolotl/state/PreKeyRecord.java | 16 ++++ .../libaxolotl/state/PreKeyStore.java | 30 +++++++ .../libaxolotl/state/SessionRecord.java | 32 ++++++++ .../libaxolotl/state/SessionState.java | 5 ++ .../libaxolotl/state/SessionStore.java | 58 +++++++++++++- .../util/{Helper.java => KeyHelper.java} | 2 +- .../textsecure/storage/SessionUtil.java | 2 +- .../storage/TextSecureSessionStore.java | 6 +- .../securesms/VerifyIdentityActivity.java | 4 +- .../crypto/KeyExchangeInitiator.java | 2 +- .../crypto/KeyExchangeProcessor.java | 4 +- 18 files changed, 333 insertions(+), 46 deletions(-) rename libaxolotl/src/main/java/org/whispersystems/libaxolotl/util/{Helper.java => KeyHelper.java} (93%) diff --git a/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemorySessionStore.java b/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemorySessionStore.java index 0339e75673..c6293c966d 100644 --- a/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemorySessionStore.java +++ b/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemorySessionStore.java @@ -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); } diff --git a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java index 2c0e80ceb3..b5761c7ed4 100644 --- a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java +++ b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java @@ -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> 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()); diff --git a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionCipherTest.java b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionCipherTest.java index f51841542a..721f9fe40d 100644 --- a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionCipherTest.java +++ b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionCipherTest.java @@ -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); diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java index 67f90a73b8..a27f829fc4 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java @@ -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. + *

+ * Sessions are built from one of three different possible vectors: + *

    + *
  1. A {@link org.whispersystems.libaxolotl.state.PreKey} retrieved from a server.
  2. + *
  3. A {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage} received from a client.
  4. + *
  5. A {@link org.whispersystems.libaxolotl.protocol.KeyExchangeMessage} sent to or received from a client.
  6. + *
+ * + * 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(), diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java index ab5ead08f4..dd910d4371 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java @@ -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 previousStates = sessionRecord.getPreviousSessionStates(); List 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(); } } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java index 091e4189be..4b6d5e60a6 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java @@ -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. + *

+ * 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 + *

+ * 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); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKey.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKey.java index a4358eb2f2..31152630a9 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKey.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKey.java @@ -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(); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyRecord.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyRecord.java index 7d2e391185..4df6b5d436 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyRecord.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyRecord.java @@ -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(); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyStore.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyStore.java index f1d778375e..934c386e29 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyStore.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/PreKeyStore.java @@ -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); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionRecord.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionRecord.java index cdb1ea69f3..2a8bf977ac 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionRecord.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionRecord.java @@ -2,12 +2,44 @@ package org.whispersystems.libaxolotl.state; import java.util.List; +/** + * A SessionRecord encapsulates the state of an ongoing session. + *

+ * 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 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(); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java index 832a60749a..53720008cc 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java @@ -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(); diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionStore.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionStore.java index 835a8f16e2..7ed7e33b41 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionStore.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionStore.java @@ -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. + *

+ * 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 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); } diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/util/Helper.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/util/KeyHelper.java similarity index 93% rename from libaxolotl/src/main/java/org/whispersystems/libaxolotl/util/Helper.java rename to libaxolotl/src/main/java/org/whispersystems/libaxolotl/util/KeyHelper.java index 7ab27ff192..5cb6351ed2 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/util/Helper.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/util/KeyHelper.java @@ -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 { diff --git a/library/src/org/whispersystems/textsecure/storage/SessionUtil.java b/library/src/org/whispersystems/textsecure/storage/SessionUtil.java index a1be9a59f2..e4f3142fec 100644 --- a/library/src/org/whispersystems/textsecure/storage/SessionUtil.java +++ b/library/src/org/whispersystems/textsecure/storage/SessionUtil.java @@ -26,7 +26,7 @@ public class SessionUtil { return sessionStore.contains(recipientId, deviceId) && - !sessionStore.get(recipientId, deviceId).getSessionState().getNeedsRefresh(); + !sessionStore.load(recipientId, deviceId).getSessionState().getNeedsRefresh(); } } diff --git a/library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java b/library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java index ecb737f8fc..36b0f27e9e 100644 --- a/library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java +++ b/library/src/org/whispersystems/textsecure/storage/TextSecureSessionStore.java @@ -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 diff --git a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java index 481d0a3ce4..2d9e7ee923 100644 --- a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -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; diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java index c686aaa78b..527cb67160 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java @@ -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(); } diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java index d7809b4f4a..c0577d5322 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java @@ -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() &&