From 926d3c929fd0c70d12fabb78e5ba100005a1b3dc Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 17 Mar 2014 17:24:00 -0700 Subject: [PATCH] Handle simultaneous initiate protocol case. 1) Modify SessionRecord to store a list of "previous" sessions in addition to the current active session. Previous sessions can be used for receiving messages, but not for sending messages. 2) When a possible "simultaneous initiate" is detected, push the current session onto the "previous session" stack instead of clearing it and starting over. 3) Additionally, mark the new session created on a received possible "simultaneous initiate" as stale for sending. The next outgoing message would trigger a full prekey refresh. 4) Work to do: outgoing messages on the SMS transport should probably not use the existing session if it's marked stale for sending. These messages need to fail and notify the user, similar to how we'll handle SMS fallback to push users before a prekey session is created. --- library/protobuf/LocalStorageProtocol.proto | 7 + .../textsecure/crypto/SessionCipherV2.java | 107 ++- .../crypto/ratchet/RatchetingSession.java | 30 +- .../textsecure/storage/Session.java | 4 +- .../textsecure/storage/SessionRecordV2.java | 467 ++-------- .../textsecure/storage/SessionState.java | 436 ++++++++++ .../textsecure/storage/StorageProtos.java | 814 +++++++++++++++++- .../securesms/crypto/DecryptingQueue.java | 4 +- .../crypto/KeyExchangeInitiator.java | 3 +- .../crypto/KeyExchangeProcessorV2.java | 72 +- .../securesms/transport/PushTransport.java | 4 +- .../securesms/transport/SmsTransport.java | 1 - 12 files changed, 1435 insertions(+), 514 deletions(-) create mode 100644 library/src/org/whispersystems/textsecure/storage/SessionState.java diff --git a/library/protobuf/LocalStorageProtocol.proto b/library/protobuf/LocalStorageProtocol.proto index db5b9f917b..89100210ea 100644 --- a/library/protobuf/LocalStorageProtocol.proto +++ b/library/protobuf/LocalStorageProtocol.proto @@ -54,6 +54,13 @@ message SessionStructure { optional uint32 remoteRegistrationId = 10; optional uint32 localRegistrationId = 11; + + optional bool needsRefresh = 12; +} + +message RecordStructure { + optional SessionStructure currentSession = 1; + repeated SessionStructure previousSessions = 2; } message PreKeyRecordStructure { diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java index 338856deee..7b55579988 100644 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java @@ -1,6 +1,7 @@ package org.whispersystems.textsecure.crypto; import android.content.Context; +import android.util.Log; import android.util.Pair; import org.whispersystems.textsecure.crypto.ecc.Curve; @@ -14,10 +15,12 @@ import org.whispersystems.textsecure.crypto.ratchet.MessageKeys; import org.whispersystems.textsecure.crypto.ratchet.RootKey; import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.storage.SessionRecordV2; +import org.whispersystems.textsecure.storage.SessionState; import org.whispersystems.textsecure.util.Conversions; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; +import java.util.List; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -45,27 +48,28 @@ public class SessionCipherV2 extends SessionCipher { public CiphertextMessage encrypt(byte[] paddedMessage) { synchronized (SESSION_LOCK) { SessionRecordV2 sessionRecord = getSessionRecord(); - ChainKey chainKey = sessionRecord.getSenderChainKey(); + SessionState sessionState = sessionRecord.getSessionState(); + ChainKey chainKey = sessionState.getSenderChainKey(); MessageKeys messageKeys = chainKey.getMessageKeys(); - ECPublicKey senderEphemeral = sessionRecord.getSenderEphemeral(); - int previousCounter = sessionRecord.getPreviousCounter(); + ECPublicKey senderEphemeral = sessionState.getSenderEphemeral(); + int previousCounter = sessionState.getPreviousCounter(); byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage); CiphertextMessage ciphertextMessage = new WhisperMessageV2(messageKeys.getMacKey(), senderEphemeral, chainKey.getIndex(), previousCounter, ciphertextBody); - if (sessionRecord.hasPendingPreKey()) { - Pair pendingPreKey = sessionRecord.getPendingPreKey(); - int localRegistrationId = sessionRecord.getLocalRegistrationId(); + if (sessionState.hasPendingPreKey()) { + Pair pendingPreKey = sessionState.getPendingPreKey(); + int localRegistrationId = sessionState.getLocalRegistrationId(); ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first, pendingPreKey.second, - sessionRecord.getLocalIdentityKey(), + sessionState.getLocalIdentityKey(), (WhisperMessageV2) ciphertextMessage); } - sessionRecord.setSenderChainKey(chainKey.getNextChainKey()); + sessionState.setSenderChainKey(chainKey.getNextChainKey()); sessionRecord.save(); return ciphertextMessage; @@ -75,50 +79,83 @@ public class SessionCipherV2 extends SessionCipher { @Override public byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException { synchronized (SESSION_LOCK) { - SessionRecordV2 sessionRecord = getSessionRecord(); - WhisperMessageV2 ciphertextMessage = new WhisperMessageV2(decodedMessage); - ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral(); - int counter = ciphertextMessage.getCounter(); - ChainKey chainKey = getOrCreateChainKey(sessionRecord, theirEphemeral); - MessageKeys messageKeys = getOrCreateMessageKeys(sessionRecord, theirEphemeral, - chainKey, counter); + SessionRecordV2 sessionRecord = getSessionRecord(); + SessionState sessionState = sessionRecord.getSessionState(); + List previousStates = sessionRecord.getPreviousSessions(); - ciphertextMessage.verifyMac(messageKeys.getMacKey()); + try { + byte[] plaintext = decrypt(sessionState, decodedMessage); + sessionRecord.save(); - byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody()); + return plaintext; + } catch (InvalidMessageException e) { + Log.w("SessionCipherV2", e); + } - sessionRecord.clearPendingPreKey(); - sessionRecord.save(); + for (SessionState previousState : previousStates) { + try { + byte[] plaintext = decrypt(previousState, decodedMessage); + sessionRecord.save(); - return plaintext; + return plaintext; + } catch (InvalidMessageException e) { + Log.w("SessionCipherV2", e); + } + } + + throw new InvalidMessageException("No valid sessions."); } } + public byte[] decrypt(SessionState sessionState, byte[] decodedMessage) + throws InvalidMessageException + { + if (!sessionState.hasSenderChain()) { + throw new InvalidMessageException("Uninitialized session!"); + } + + WhisperMessageV2 ciphertextMessage = new WhisperMessageV2(decodedMessage); + ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral(); + int counter = ciphertextMessage.getCounter(); + ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral); + MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral, + chainKey, counter); + + ciphertextMessage.verifyMac(messageKeys.getMacKey()); + + byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody()); + + sessionState.clearPendingPreKey(); + + return plaintext; + + } + @Override public int getRemoteRegistrationId() { synchronized (SESSION_LOCK) { SessionRecordV2 sessionRecord = getSessionRecord(); - return sessionRecord.getRemoteRegistrationId(); + return sessionRecord.getSessionState().getRemoteRegistrationId(); } } - private ChainKey getOrCreateChainKey(SessionRecordV2 sessionRecord, ECPublicKey theirEphemeral) + private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral) throws InvalidMessageException { try { - if (sessionRecord.hasReceiverChain(theirEphemeral)) { - return sessionRecord.getReceiverChainKey(theirEphemeral); + if (sessionState.hasReceiverChain(theirEphemeral)) { + return sessionState.getReceiverChainKey(theirEphemeral); } else { - RootKey rootKey = sessionRecord.getRootKey(); - ECKeyPair ourEphemeral = sessionRecord.getSenderEphemeralPair(); + RootKey rootKey = sessionState.getRootKey(); + ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair(); Pair receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral); ECKeyPair ourNewEphemeral = Curve.generateKeyPairForType(Curve.DJB_TYPE); Pair senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral); - sessionRecord.setRootKey(senderChain.first); - sessionRecord.addReceiverChain(theirEphemeral, receiverChain.second); - sessionRecord.setPreviousCounter(sessionRecord.getSenderChainKey().getIndex()-1); - sessionRecord.setSenderChain(ourNewEphemeral, senderChain.second); + sessionState.setRootKey(senderChain.first); + sessionState.addReceiverChain(theirEphemeral, receiverChain.second); + sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1); + sessionState.setSenderChain(ourNewEphemeral, senderChain.second); return receiverChain.second; } @@ -127,14 +164,14 @@ public class SessionCipherV2 extends SessionCipher { } } - private MessageKeys getOrCreateMessageKeys(SessionRecordV2 sessionRecord, + private MessageKeys getOrCreateMessageKeys(SessionState sessionState, ECPublicKey theirEphemeral, ChainKey chainKey, int counter) throws InvalidMessageException { if (chainKey.getIndex() > counter) { - if (sessionRecord.hasMessageKeys(theirEphemeral, counter)) { - return sessionRecord.removeMessageKeys(theirEphemeral, counter); + if (sessionState.hasMessageKeys(theirEphemeral, counter)) { + return sessionState.removeMessageKeys(theirEphemeral, counter); } else { throw new InvalidMessageException("Received message with old counter!"); } @@ -146,11 +183,11 @@ public class SessionCipherV2 extends SessionCipher { while (chainKey.getIndex() < counter) { MessageKeys messageKeys = chainKey.getMessageKeys(); - sessionRecord.setMessageKeys(theirEphemeral, messageKeys); + sessionState.setMessageKeys(theirEphemeral, messageKeys); chainKey = chainKey.getNextChainKey(); } - sessionRecord.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey()); + sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey()); return chainKey.getMessageKeys(); } diff --git a/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java b/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java index 2548fa3abe..924b163935 100644 --- a/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java +++ b/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java @@ -10,14 +10,14 @@ import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets; import org.whispersystems.textsecure.crypto.kdf.HKDF; -import org.whispersystems.textsecure.storage.SessionRecordV2; +import org.whispersystems.textsecure.storage.SessionState; import java.io.ByteArrayOutputStream; import java.io.IOException; public class RatchetingSession { - public static void initializeSession(SessionRecordV2 sessionRecord, + public static void initializeSession(SessionState sessionState, ECKeyPair ourBaseKey, ECPublicKey theirBaseKey, ECKeyPair ourEphemeralKey, @@ -27,48 +27,48 @@ public class RatchetingSession { throws InvalidKeyException { if (isAlice(ourBaseKey.getPublicKey(), theirBaseKey, ourEphemeralKey.getPublicKey(), theirEphemeralKey)) { - initializeSessionAsAlice(sessionRecord, ourBaseKey, theirBaseKey, theirEphemeralKey, + initializeSessionAsAlice(sessionState, ourBaseKey, theirBaseKey, theirEphemeralKey, ourIdentityKey, theirIdentityKey); } else { - initializeSessionAsBob(sessionRecord, ourBaseKey, theirBaseKey, + initializeSessionAsBob(sessionState, ourBaseKey, theirBaseKey, ourEphemeralKey, ourIdentityKey, theirIdentityKey); } } - private static void initializeSessionAsAlice(SessionRecordV2 sessionRecord, + private static void initializeSessionAsAlice(SessionState sessionState, ECKeyPair ourBaseKey, ECPublicKey theirBaseKey, ECPublicKey theirEphemeralKey, IdentityKeyPair ourIdentityKey, IdentityKey theirIdentityKey) throws InvalidKeyException { - sessionRecord.setRemoteIdentityKey(theirIdentityKey); - sessionRecord.setLocalIdentityKey(ourIdentityKey.getPublicKey()); + sessionState.setRemoteIdentityKey(theirIdentityKey); + sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey()); ECKeyPair sendingKey = Curve.generateKeyPairForType(ourIdentityKey.getPublicKey().getPublicKey().getType()); Pair receivingChain = calculate3DHE(true, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey); Pair sendingChain = receivingChain.first.createChain(theirEphemeralKey, sendingKey); - sessionRecord.addReceiverChain(theirEphemeralKey, receivingChain.second); - sessionRecord.setSenderChain(sendingKey, sendingChain.second); - sessionRecord.setRootKey(sendingChain.first); + sessionState.addReceiverChain(theirEphemeralKey, receivingChain.second); + sessionState.setSenderChain(sendingKey, sendingChain.second); + sessionState.setRootKey(sendingChain.first); } - private static void initializeSessionAsBob(SessionRecordV2 sessionRecord, + private static void initializeSessionAsBob(SessionState sessionState, ECKeyPair ourBaseKey, ECPublicKey theirBaseKey, ECKeyPair ourEphemeralKey, IdentityKeyPair ourIdentityKey, IdentityKey theirIdentityKey) throws InvalidKeyException { - sessionRecord.setRemoteIdentityKey(theirIdentityKey); - sessionRecord.setLocalIdentityKey(ourIdentityKey.getPublicKey()); + sessionState.setRemoteIdentityKey(theirIdentityKey); + sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey()); Pair sendingChain = calculate3DHE(false, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey); - sessionRecord.setSenderChain(ourEphemeralKey, sendingChain.second); - sessionRecord.setRootKey(sendingChain.first); + sessionState.setSenderChain(ourEphemeralKey, sendingChain.second); + sessionState.setRootKey(sendingChain.first); } private static Pair calculate3DHE(boolean isAlice, diff --git a/library/src/org/whispersystems/textsecure/storage/Session.java b/library/src/org/whispersystems/textsecure/storage/Session.java index b016e211cc..8f44a6d714 100644 --- a/library/src/org/whispersystems/textsecure/storage/Session.java +++ b/library/src/org/whispersystems/textsecure/storage/Session.java @@ -49,7 +49,6 @@ public class Session { return SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID); } - private static boolean hasV1Session(Context context, CanonicalRecipient recipient) { return SessionRecordV1.hasSession(context, recipient) && RemoteKeyRecord.hasRecord(context, recipient) && @@ -70,7 +69,8 @@ public class Session { RecipientDevice.DEFAULT_DEVICE_ID)) { return new SessionRecordV2(context, masterSecret, recipientId, - RecipientDevice.DEFAULT_DEVICE_ID).getRemoteIdentityKey(); + RecipientDevice.DEFAULT_DEVICE_ID).getSessionState() + .getRemoteIdentityKey(); } else if (SessionRecordV1.hasSession(context, recipientId)) { return new SessionRecordV1(context, masterSecret, recipientId).getIdentityKey(); } else { diff --git a/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java b/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java index e4c5ba39f7..46fec98a67 100644 --- a/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java +++ b/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java @@ -18,26 +18,10 @@ package org.whispersystems.textsecure.storage; import android.content.Context; import android.util.Log; -import android.util.Pair; -import com.google.protobuf.ByteString; - -import org.whispersystems.textsecure.crypto.IdentityKey; -import org.whispersystems.textsecure.crypto.IdentityKeyPair; -import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.ecc.Curve; -import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; -import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; -import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; -import org.whispersystems.textsecure.crypto.ratchet.ChainKey; -import org.whispersystems.textsecure.crypto.ratchet.MessageKeys; -import org.whispersystems.textsecure.crypto.ratchet.RootKey; -import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain; -import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange; -import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey; import java.io.File; import java.io.FileInputStream; @@ -45,11 +29,11 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import javax.crypto.spec.SecretKeySpec; +import static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure; +import static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure; /** * A disk record representing a current session. @@ -60,11 +44,15 @@ import javax.crypto.spec.SecretKeySpec; public class SessionRecordV2 extends Record { private static final Object FILE_LOCK = new Object(); - private static final int CURRENT_VERSION = 1; + + private static final int SINGLE_STATE_VERSION = 1; + private static final int ARCHIVE_STATES_VERSION = 2; + private static final int CURRENT_VERSION = 2; private final MasterSecret masterSecret; - private StorageProtos.SessionStructure sessionStructure = - StorageProtos.SessionStructure.newBuilder().build(); + + private SessionState sessionState = new SessionState(SessionStructure.newBuilder().build()); + private List previousStates = new LinkedList(); public SessionRecordV2(Context context, MasterSecret masterSecret, RecipientDevice peer) { this(context, masterSecret, peer.getRecipientId(), peer.getDeviceId()); @@ -80,6 +68,15 @@ public class SessionRecordV2 extends Record { return recipientId + (deviceId == RecipientDevice.DEFAULT_DEVICE_ID ? "" : "." + deviceId); } + public SessionState getSessionState() { + return sessionState; + } + + + public List getPreviousSessions() { + return previousStates; + } + public static List getSessionSubDevices(Context context, CanonicalRecipient recipient) { List results = new LinkedList(); File parent = getParentDirectory(context, SESSIONS_DIRECTORY_V2); @@ -129,404 +126,49 @@ public class SessionRecordV2 extends Record { long recipientId, int deviceId) { return hasRecord(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientId, deviceId)) && - new SessionRecordV2(context, masterSecret, recipientId, deviceId).hasSenderChain(); + new SessionRecordV2(context, masterSecret, recipientId, deviceId).sessionState.hasSenderChain(); + } + + public static boolean needsRefresh(Context context, MasterSecret masterSecret, + RecipientDevice recipient) + { + return new SessionRecordV2(context, masterSecret, + recipient.getRecipientId(), + recipient.getDeviceId()).getSessionState() + .getNeedsRefresh(); } public void clear() { - this.sessionStructure = StorageProtos.SessionStructure.newBuilder().build(); + this.sessionState = new SessionState(SessionStructure.newBuilder().build()); + this.previousStates = new LinkedList(); } - public void setSessionVersion(int version) { - this.sessionStructure = this.sessionStructure.toBuilder() - .setSessionVersion(version) - .build(); - } - - public int getSessionVersion() { - return this.sessionStructure.getSessionVersion(); - } - - public void setRemoteIdentityKey(IdentityKey identityKey) { - this.sessionStructure = this.sessionStructure.toBuilder() - .setRemoteIdentityPublic(ByteString.copyFrom(identityKey.serialize())) - .build(); - } - - public void setLocalIdentityKey(IdentityKey identityKey) { - this.sessionStructure = this.sessionStructure.toBuilder() - .setLocalIdentityPublic(ByteString.copyFrom(identityKey.serialize())) - .build(); - } - - public IdentityKey getRemoteIdentityKey() { - try { - if (!this.sessionStructure.hasRemoteIdentityPublic()) { - return null; - } - - return new IdentityKey(this.sessionStructure.getRemoteIdentityPublic().toByteArray(), 0); - } catch (InvalidKeyException e) { - Log.w("SessionRecordV2", e); - return null; - } - } - - public IdentityKey getLocalIdentityKey() { - try { - return new IdentityKey(this.sessionStructure.getLocalIdentityPublic().toByteArray(), 0); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } - } - - public int getPreviousCounter() { - return sessionStructure.getPreviousCounter(); - } - - public void setPreviousCounter(int previousCounter) { - this.sessionStructure = this.sessionStructure.toBuilder() - .setPreviousCounter(previousCounter) - .build(); - } - - public RootKey getRootKey() { - return new RootKey(this.sessionStructure.getRootKey().toByteArray()); - } - - public void setRootKey(RootKey rootKey) { - this.sessionStructure = this.sessionStructure.toBuilder() - .setRootKey(ByteString.copyFrom(rootKey.getKeyBytes())) - .build(); - } - - public ECPublicKey getSenderEphemeral() { - try { - return Curve.decodePoint(sessionStructure.getSenderChain().getSenderEphemeral().toByteArray(), 0); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } - } - - public ECKeyPair getSenderEphemeralPair() { - ECPublicKey publicKey = getSenderEphemeral(); - ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), - sessionStructure.getSenderChain() - .getSenderEphemeralPrivate() - .toByteArray()); - - return new ECKeyPair(publicKey, privateKey); - } - - public boolean hasReceiverChain(ECPublicKey senderEphemeral) { - return getReceiverChain(senderEphemeral) != null; - } - - public boolean hasSenderChain() { - return sessionStructure.hasSenderChain(); - } - - private Pair getReceiverChain(ECPublicKey senderEphemeral) { - List receiverChains = sessionStructure.getReceiverChainsList(); - int index = 0; - - for (Chain receiverChain : receiverChains) { - try { - ECPublicKey chainSenderEphemeral = Curve.decodePoint(receiverChain.getSenderEphemeral().toByteArray(), 0); - - if (chainSenderEphemeral.equals(senderEphemeral)) { - return new Pair(receiverChain,index); - } - } catch (InvalidKeyException e) { - Log.w("SessionRecordV2", e); - } - - index++; - } - - return null; - } - - public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) { - Pair receiverChainAndIndex = getReceiverChain(senderEphemeral); - Chain receiverChain = receiverChainAndIndex.first; - - if (receiverChain == null) { - return null; - } else { - return new ChainKey(receiverChain.getChainKey().getKey().toByteArray(), - receiverChain.getChainKey().getIndex()); - } - } - - public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey) { - Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder() - .setKey(ByteString.copyFrom(chainKey.getKey())) - .setIndex(chainKey.getIndex()) - .build(); - - Chain chain = Chain.newBuilder() - .setChainKey(chainKeyStructure) - .setSenderEphemeral(ByteString.copyFrom(senderEphemeral.serialize())) - .build(); - - this.sessionStructure = this.sessionStructure.toBuilder().addReceiverChains(chain).build(); - - if (this.sessionStructure.getReceiverChainsList().size() > 5) { - this.sessionStructure = this.sessionStructure.toBuilder() - .removeReceiverChains(0) - .build(); - } - } - - public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey) { - Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder() - .setKey(ByteString.copyFrom(chainKey.getKey())) - .setIndex(chainKey.getIndex()) - .build(); - - Chain senderChain = Chain.newBuilder() - .setSenderEphemeral(ByteString.copyFrom(senderEphemeralPair.getPublicKey().serialize())) - .setSenderEphemeralPrivate(ByteString.copyFrom(senderEphemeralPair.getPrivateKey().serialize())) - .setChainKey(chainKeyStructure) - .build(); - - this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(senderChain).build(); - } - - public ChainKey getSenderChainKey() { - Chain.ChainKey chainKeyStructure = sessionStructure.getSenderChain().getChainKey(); - return new ChainKey(chainKeyStructure.getKey().toByteArray(), chainKeyStructure.getIndex()); - } - - - public void setSenderChainKey(ChainKey nextChainKey) { - Chain.ChainKey chainKey = Chain.ChainKey.newBuilder() - .setKey(ByteString.copyFrom(nextChainKey.getKey())) - .setIndex(nextChainKey.getIndex()) - .build(); - - Chain chain = sessionStructure.getSenderChain().toBuilder() - .setChainKey(chainKey).build(); - - this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(chain).build(); - } - - public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) { - Pair chainAndIndex = getReceiverChain(senderEphemeral); - Chain chain = chainAndIndex.first; - - if (chain == null) { - return false; - } - - List messageKeyList = chain.getMessageKeysList(); - - for (Chain.MessageKey messageKey : messageKeyList) { - if (messageKey.getIndex() == counter) { - return true; - } - } - - return false; - } - - public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) { - Pair chainAndIndex = getReceiverChain(senderEphemeral); - Chain chain = chainAndIndex.first; - - if (chain == null) { - return null; - } - - List messageKeyList = new LinkedList(chain.getMessageKeysList()); - Iterator messageKeyIterator = messageKeyList.iterator(); - MessageKeys result = null; - - while (messageKeyIterator.hasNext()) { - Chain.MessageKey messageKey = messageKeyIterator.next(); - - if (messageKey.getIndex() == counter) { - result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"), - new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"), - messageKey.getIndex()); - - messageKeyIterator.remove(); - break; - } - } - - Chain updatedChain = chain.toBuilder().clearMessageKeys() - .addAllMessageKeys(messageKeyList) - .build(); - - this.sessionStructure = this.sessionStructure.toBuilder() - .setReceiverChains(chainAndIndex.second, updatedChain) - .build(); - - return result; - } - - public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) { - Pair chainAndIndex = getReceiverChain(senderEphemeral); - Chain chain = chainAndIndex.first; - Chain.MessageKey messageKeyStructure = Chain.MessageKey.newBuilder() - .setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded())) - .setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded())) - .setIndex(messageKeys.getCounter()) - .build(); - - Chain updatedChain = chain.toBuilder() - .addMessageKeys(messageKeyStructure) - .build(); - - this.sessionStructure = this.sessionStructure.toBuilder() - .setReceiverChains(chainAndIndex.second, updatedChain) - .build(); - } - - public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) { - Pair chainAndIndex = getReceiverChain(senderEphemeral); - Chain chain = chainAndIndex.first; - - Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder() - .setKey(ByteString.copyFrom(chainKey.getKey())) - .setIndex(chainKey.getIndex()) - .build(); - - Chain updatedChain = chain.toBuilder().setChainKey(chainKeyStructure).build(); - - this.sessionStructure = this.sessionStructure.toBuilder() - .setReceiverChains(chainAndIndex.second, updatedChain) - .build(); - } - - public void setPendingKeyExchange(int sequence, - ECKeyPair ourBaseKey, - ECKeyPair ourEphemeralKey, - IdentityKeyPair ourIdentityKey) - { - PendingKeyExchange structure = - PendingKeyExchange.newBuilder() - .setSequence(sequence) - .setLocalBaseKey(ByteString.copyFrom(ourBaseKey.getPublicKey().serialize())) - .setLocalBaseKeyPrivate(ByteString.copyFrom(ourBaseKey.getPrivateKey().serialize())) - .setLocalEphemeralKey(ByteString.copyFrom(ourEphemeralKey.getPublicKey().serialize())) - .setLocalEphemeralKeyPrivate(ByteString.copyFrom(ourEphemeralKey.getPrivateKey().serialize())) - .setLocalIdentityKey(ByteString.copyFrom(ourIdentityKey.getPublicKey().serialize())) - .setLocalIdentityKeyPrivate(ByteString.copyFrom(ourIdentityKey.getPrivateKey().serialize())) - .build(); - - this.sessionStructure = this.sessionStructure.toBuilder() - .setPendingKeyExchange(structure) - .build(); - } - - public int getPendingKeyExchangeSequence() { - return sessionStructure.getPendingKeyExchange().getSequence(); - } - - public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException { - ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange() - .getLocalBaseKey().toByteArray(), 0); - - ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), - sessionStructure.getPendingKeyExchange() - .getLocalBaseKeyPrivate() - .toByteArray()); - - return new ECKeyPair(publicKey, privateKey); - } - - public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException { - ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange() - .getLocalEphemeralKey().toByteArray(), 0); - - ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), - sessionStructure.getPendingKeyExchange() - .getLocalEphemeralKeyPrivate() - .toByteArray()); - - return new ECKeyPair(publicKey, privateKey); - } - - public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException { - IdentityKey publicKey = new IdentityKey(sessionStructure.getPendingKeyExchange() - .getLocalIdentityKey().toByteArray(), 0); - - ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getPublicKey().getType(), - sessionStructure.getPendingKeyExchange() - .getLocalIdentityKeyPrivate() - .toByteArray()); - - return new IdentityKeyPair(publicKey, privateKey); - } - - public boolean hasPendingKeyExchange() { - return sessionStructure.hasPendingKeyExchange(); - } - - public void setPendingPreKey(int preKeyId, ECPublicKey baseKey) { - PendingPreKey pending = PendingPreKey.newBuilder() - .setPreKeyId(preKeyId) - .setBaseKey(ByteString.copyFrom(baseKey.serialize())) - .build(); - - this.sessionStructure = this.sessionStructure.toBuilder() - .setPendingPreKey(pending) - .build(); - } - - public boolean hasPendingPreKey() { - return this.sessionStructure.hasPendingPreKey(); - } - - public Pair getPendingPreKey() { - try { - return new Pair(sessionStructure.getPendingPreKey().getPreKeyId(), - Curve.decodePoint(sessionStructure.getPendingPreKey() - .getBaseKey() - .toByteArray(), 0)); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } - } - - public void clearPendingPreKey() { - this.sessionStructure = this.sessionStructure.toBuilder() - .clearPendingPreKey() - .build(); - } - - public void setRemoteRegistrationId(int registrationId) { - this.sessionStructure = this.sessionStructure.toBuilder() - .setRemoteRegistrationId(registrationId) - .build(); - } - - public int getRemoteRegistrationId() { - return this.sessionStructure.getRemoteRegistrationId(); - } - - public void setLocalRegistrationId(int registrationId) { - this.sessionStructure = this.sessionStructure.toBuilder() - .setLocalRegistrationId(registrationId) - .build(); - } - - public int getLocalRegistrationId() { - return this.sessionStructure.getLocalRegistrationId(); + public void archiveCurrentState() { + this.previousStates.add(sessionState); + this.sessionState = new SessionState(SessionStructure.newBuilder().build()); } public void save() { synchronized (FILE_LOCK) { try { + List previousStructures = new LinkedList(); + + for (SessionState previousState : previousStates) { + previousStructures.add(previousState.getStructure()); + } + + RecordStructure record = RecordStructure.newBuilder() + .setCurrentSession(sessionState.getStructure()) + .addAllPreviousSessions(previousStructures) + .build(); + RandomAccessFile file = openRandomAccessFile(); FileChannel out = file.getChannel(); out.position(0); MasterCipher cipher = new MasterCipher(masterSecret); writeInteger(CURRENT_VERSION, out); - writeBlob(cipher.encryptBytes(sessionStructure.toByteArray()), out); + writeBlob(cipher.encryptBytes(record.toByteArray()), out); out.truncate(out.position()); file.close(); @@ -549,11 +191,26 @@ public class SessionRecordV2 extends Record { MasterCipher cipher = new MasterCipher(masterSecret); byte[] encryptedBlob = readBlob(in); + if (versionMarker == SINGLE_STATE_VERSION) { + byte[] plaintextBytes = cipher.decryptBytes(encryptedBlob); + SessionStructure sessionStructure = SessionStructure.parseFrom(plaintextBytes); + this.sessionState = new SessionState(sessionStructure); + } else if (versionMarker == ARCHIVE_STATES_VERSION) { + byte[] plaintextBytes = cipher.decryptBytes(encryptedBlob); + RecordStructure recordStructure = RecordStructure.parseFrom(plaintextBytes); - this.sessionStructure = StorageProtos.SessionStructure - .parseFrom(cipher.decryptBytes(encryptedBlob)); + this.sessionState = new SessionState(recordStructure.getCurrentSession()); + this.previousStates = new LinkedList(); + + for (SessionStructure sessionStructure : recordStructure.getPreviousSessionsList()) { + this.previousStates.add(new SessionState(sessionStructure)); + } + } else { + throw new AssertionError("Unknown version: " + versionMarker); + } in.close(); + } catch (FileNotFoundException e) { Log.w("SessionRecordV2", "No session information found."); // XXX diff --git a/library/src/org/whispersystems/textsecure/storage/SessionState.java b/library/src/org/whispersystems/textsecure/storage/SessionState.java new file mode 100644 index 0000000000..9e03dcfaa8 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/storage/SessionState.java @@ -0,0 +1,436 @@ +package org.whispersystems.textsecure.storage; + +import android.util.Log; +import android.util.Pair; + +import com.google.protobuf.ByteString; + +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.IdentityKeyPair; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.crypto.ratchet.ChainKey; +import org.whispersystems.textsecure.crypto.ratchet.MessageKeys; +import org.whispersystems.textsecure.crypto.ratchet.RootKey; +import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain; +import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange; +import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.crypto.spec.SecretKeySpec; + +import static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure; + +public class SessionState { + + private SessionStructure sessionStructure; + + public SessionState(SessionStructure sessionStructure) { + this.sessionStructure = sessionStructure; + } + + public SessionStructure getStructure() { + return sessionStructure; + } + + 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) + .build(); + } + + public int getSessionVersion() { + return this.sessionStructure.getSessionVersion(); + } + + public void setRemoteIdentityKey(IdentityKey identityKey) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setRemoteIdentityPublic(ByteString.copyFrom(identityKey.serialize())) + .build(); + } + + public void setLocalIdentityKey(IdentityKey identityKey) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setLocalIdentityPublic(ByteString.copyFrom(identityKey.serialize())) + .build(); + } + + public IdentityKey getRemoteIdentityKey() { + try { + if (!this.sessionStructure.hasRemoteIdentityPublic()) { + return null; + } + + return new IdentityKey(this.sessionStructure.getRemoteIdentityPublic().toByteArray(), 0); + } catch (InvalidKeyException e) { + Log.w("SessionRecordV2", e); + return null; + } + } + + public IdentityKey getLocalIdentityKey() { + try { + return new IdentityKey(this.sessionStructure.getLocalIdentityPublic().toByteArray(), 0); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + public int getPreviousCounter() { + return sessionStructure.getPreviousCounter(); + } + + public void setPreviousCounter(int previousCounter) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setPreviousCounter(previousCounter) + .build(); + } + + public RootKey getRootKey() { + return new RootKey(this.sessionStructure.getRootKey().toByteArray()); + } + + public void setRootKey(RootKey rootKey) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setRootKey(ByteString.copyFrom(rootKey.getKeyBytes())) + .build(); + } + + public ECPublicKey getSenderEphemeral() { + try { + return Curve.decodePoint(sessionStructure.getSenderChain().getSenderEphemeral().toByteArray(), 0); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + public ECKeyPair getSenderEphemeralPair() { + ECPublicKey publicKey = getSenderEphemeral(); + ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), + sessionStructure.getSenderChain() + .getSenderEphemeralPrivate() + .toByteArray()); + + return new ECKeyPair(publicKey, privateKey); + } + + public boolean hasReceiverChain(ECPublicKey senderEphemeral) { + return getReceiverChain(senderEphemeral) != null; + } + + public boolean hasSenderChain() { + return sessionStructure.hasSenderChain(); + } + + private Pair getReceiverChain(ECPublicKey senderEphemeral) { + List receiverChains = sessionStructure.getReceiverChainsList(); + int index = 0; + + for (Chain receiverChain : receiverChains) { + try { + ECPublicKey chainSenderEphemeral = Curve.decodePoint(receiverChain.getSenderEphemeral().toByteArray(), 0); + + if (chainSenderEphemeral.equals(senderEphemeral)) { + return new Pair(receiverChain,index); + } + } catch (InvalidKeyException e) { + Log.w("SessionRecordV2", e); + } + + index++; + } + + return null; + } + + public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) { + Pair receiverChainAndIndex = getReceiverChain(senderEphemeral); + Chain receiverChain = receiverChainAndIndex.first; + + if (receiverChain == null) { + return null; + } else { + return new ChainKey(receiverChain.getChainKey().getKey().toByteArray(), + receiverChain.getChainKey().getIndex()); + } + } + + public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey) { + Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder() + .setKey(ByteString.copyFrom(chainKey.getKey())) + .setIndex(chainKey.getIndex()) + .build(); + + Chain chain = Chain.newBuilder() + .setChainKey(chainKeyStructure) + .setSenderEphemeral(ByteString.copyFrom(senderEphemeral.serialize())) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder().addReceiverChains(chain).build(); + + if (this.sessionStructure.getReceiverChainsList().size() > 5) { + this.sessionStructure = this.sessionStructure.toBuilder() + .removeReceiverChains(0) + .build(); + } + } + + public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey) { + Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder() + .setKey(ByteString.copyFrom(chainKey.getKey())) + .setIndex(chainKey.getIndex()) + .build(); + + Chain senderChain = Chain.newBuilder() + .setSenderEphemeral(ByteString.copyFrom(senderEphemeralPair.getPublicKey().serialize())) + .setSenderEphemeralPrivate(ByteString.copyFrom(senderEphemeralPair.getPrivateKey().serialize())) + .setChainKey(chainKeyStructure) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(senderChain).build(); + } + + public ChainKey getSenderChainKey() { + Chain.ChainKey chainKeyStructure = sessionStructure.getSenderChain().getChainKey(); + return new ChainKey(chainKeyStructure.getKey().toByteArray(), chainKeyStructure.getIndex()); + } + + + public void setSenderChainKey(ChainKey nextChainKey) { + Chain.ChainKey chainKey = Chain.ChainKey.newBuilder() + .setKey(ByteString.copyFrom(nextChainKey.getKey())) + .setIndex(nextChainKey.getIndex()) + .build(); + + Chain chain = sessionStructure.getSenderChain().toBuilder() + .setChainKey(chainKey).build(); + + this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(chain).build(); + } + + public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) { + Pair chainAndIndex = getReceiverChain(senderEphemeral); + Chain chain = chainAndIndex.first; + + if (chain == null) { + return false; + } + + List messageKeyList = chain.getMessageKeysList(); + + for (Chain.MessageKey messageKey : messageKeyList) { + if (messageKey.getIndex() == counter) { + return true; + } + } + + return false; + } + + public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) { + Pair chainAndIndex = getReceiverChain(senderEphemeral); + Chain chain = chainAndIndex.first; + + if (chain == null) { + return null; + } + + List messageKeyList = new LinkedList(chain.getMessageKeysList()); + Iterator messageKeyIterator = messageKeyList.iterator(); + MessageKeys result = null; + + while (messageKeyIterator.hasNext()) { + Chain.MessageKey messageKey = messageKeyIterator.next(); + + if (messageKey.getIndex() == counter) { + result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"), + new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"), + messageKey.getIndex()); + + messageKeyIterator.remove(); + break; + } + } + + Chain updatedChain = chain.toBuilder().clearMessageKeys() + .addAllMessageKeys(messageKeyList) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder() + .setReceiverChains(chainAndIndex.second, updatedChain) + .build(); + + return result; + } + + public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) { + Pair chainAndIndex = getReceiverChain(senderEphemeral); + Chain chain = chainAndIndex.first; + Chain.MessageKey messageKeyStructure = Chain.MessageKey.newBuilder() + .setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded())) + .setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded())) + .setIndex(messageKeys.getCounter()) + .build(); + + Chain updatedChain = chain.toBuilder() + .addMessageKeys(messageKeyStructure) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder() + .setReceiverChains(chainAndIndex.second, updatedChain) + .build(); + } + + public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) { + Pair chainAndIndex = getReceiverChain(senderEphemeral); + Chain chain = chainAndIndex.first; + + Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder() + .setKey(ByteString.copyFrom(chainKey.getKey())) + .setIndex(chainKey.getIndex()) + .build(); + + Chain updatedChain = chain.toBuilder().setChainKey(chainKeyStructure).build(); + + this.sessionStructure = this.sessionStructure.toBuilder() + .setReceiverChains(chainAndIndex.second, updatedChain) + .build(); + } + + public void setPendingKeyExchange(int sequence, + ECKeyPair ourBaseKey, + ECKeyPair ourEphemeralKey, + IdentityKeyPair ourIdentityKey) + { + PendingKeyExchange structure = + PendingKeyExchange.newBuilder() + .setSequence(sequence) + .setLocalBaseKey(ByteString.copyFrom(ourBaseKey.getPublicKey().serialize())) + .setLocalBaseKeyPrivate(ByteString.copyFrom(ourBaseKey.getPrivateKey().serialize())) + .setLocalEphemeralKey(ByteString.copyFrom(ourEphemeralKey.getPublicKey().serialize())) + .setLocalEphemeralKeyPrivate(ByteString.copyFrom(ourEphemeralKey.getPrivateKey().serialize())) + .setLocalIdentityKey(ByteString.copyFrom(ourIdentityKey.getPublicKey().serialize())) + .setLocalIdentityKeyPrivate(ByteString.copyFrom(ourIdentityKey.getPrivateKey().serialize())) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder() + .setPendingKeyExchange(structure) + .build(); + } + + public int getPendingKeyExchangeSequence() { + return sessionStructure.getPendingKeyExchange().getSequence(); + } + + public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException { + ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange() + .getLocalBaseKey().toByteArray(), 0); + + ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), + sessionStructure.getPendingKeyExchange() + .getLocalBaseKeyPrivate() + .toByteArray()); + + return new ECKeyPair(publicKey, privateKey); + } + + public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException { + ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange() + .getLocalEphemeralKey().toByteArray(), 0); + + ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), + sessionStructure.getPendingKeyExchange() + .getLocalEphemeralKeyPrivate() + .toByteArray()); + + return new ECKeyPair(publicKey, privateKey); + } + + public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException { + IdentityKey publicKey = new IdentityKey(sessionStructure.getPendingKeyExchange() + .getLocalIdentityKey().toByteArray(), 0); + + ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getPublicKey().getType(), + sessionStructure.getPendingKeyExchange() + .getLocalIdentityKeyPrivate() + .toByteArray()); + + return new IdentityKeyPair(publicKey, privateKey); + } + + public boolean hasPendingKeyExchange() { + return sessionStructure.hasPendingKeyExchange(); + } + + public void setPendingPreKey(int preKeyId, ECPublicKey baseKey) { + PendingPreKey pending = PendingPreKey.newBuilder() + .setPreKeyId(preKeyId) + .setBaseKey(ByteString.copyFrom(baseKey.serialize())) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder() + .setPendingPreKey(pending) + .build(); + } + + public boolean hasPendingPreKey() { + return this.sessionStructure.hasPendingPreKey(); + } + + public Pair getPendingPreKey() { + try { + return new Pair(sessionStructure.getPendingPreKey().getPreKeyId(), + Curve.decodePoint(sessionStructure.getPendingPreKey() + .getBaseKey() + .toByteArray(), 0)); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + public void clearPendingPreKey() { + this.sessionStructure = this.sessionStructure.toBuilder() + .clearPendingPreKey() + .build(); + } + + public void setRemoteRegistrationId(int registrationId) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setRemoteRegistrationId(registrationId) + .build(); + } + + public int getRemoteRegistrationId() { + return this.sessionStructure.getRemoteRegistrationId(); + } + + public void setLocalRegistrationId(int registrationId) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setLocalRegistrationId(registrationId) + .build(); + } + + public int getLocalRegistrationId() { + return this.sessionStructure.getLocalRegistrationId(); + } + + public byte[] serialize() { + return sessionStructure.toByteArray(); + } +} diff --git a/library/src/org/whispersystems/textsecure/storage/StorageProtos.java b/library/src/org/whispersystems/textsecure/storage/StorageProtos.java index 898745dcca..aac61042fc 100644 --- a/library/src/org/whispersystems/textsecure/storage/StorageProtos.java +++ b/library/src/org/whispersystems/textsecure/storage/StorageProtos.java @@ -63,6 +63,10 @@ public final class StorageProtos { // optional uint32 localRegistrationId = 11; boolean hasLocalRegistrationId(); int getLocalRegistrationId(); + + // optional bool needsRefresh = 12; + boolean hasNeedsRefresh(); + boolean getNeedsRefresh(); } public static final class SessionStructure extends com.google.protobuf.GeneratedMessage @@ -2992,6 +2996,16 @@ public final class StorageProtos { return localRegistrationId_; } + // optional bool needsRefresh = 12; + public static final int NEEDSREFRESH_FIELD_NUMBER = 12; + private boolean needsRefresh_; + public boolean hasNeedsRefresh() { + return ((bitField0_ & 0x00000400) == 0x00000400); + } + public boolean getNeedsRefresh() { + return needsRefresh_; + } + private void initFields() { sessionVersion_ = 0; localIdentityPublic_ = com.google.protobuf.ByteString.EMPTY; @@ -3004,6 +3018,7 @@ public final class StorageProtos { pendingPreKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance(); remoteRegistrationId_ = 0; localRegistrationId_ = 0; + needsRefresh_ = false; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -3050,6 +3065,9 @@ public final class StorageProtos { if (((bitField0_ & 0x00000200) == 0x00000200)) { output.writeUInt32(11, localRegistrationId_); } + if (((bitField0_ & 0x00000400) == 0x00000400)) { + output.writeBool(12, needsRefresh_); + } getUnknownFields().writeTo(output); } @@ -3103,6 +3121,10 @@ public final class StorageProtos { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(11, localRegistrationId_); } + if (((bitField0_ & 0x00000400) == 0x00000400)) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(12, needsRefresh_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -3269,6 +3291,8 @@ public final class StorageProtos { bitField0_ = (bitField0_ & ~0x00000200); localRegistrationId_ = 0; bitField0_ = (bitField0_ & ~0x00000400); + needsRefresh_ = false; + bitField0_ = (bitField0_ & ~0x00000800); return this; } @@ -3368,6 +3392,10 @@ public final class StorageProtos { to_bitField0_ |= 0x00000200; } result.localRegistrationId_ = localRegistrationId_; + if (((from_bitField0_ & 0x00000800) == 0x00000800)) { + to_bitField0_ |= 0x00000400; + } + result.needsRefresh_ = needsRefresh_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -3440,6 +3468,9 @@ public final class StorageProtos { if (other.hasLocalRegistrationId()) { setLocalRegistrationId(other.getLocalRegistrationId()); } + if (other.hasNeedsRefresh()) { + setNeedsRefresh(other.getNeedsRefresh()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -3539,6 +3570,11 @@ public final class StorageProtos { localRegistrationId_ = input.readUInt32(); break; } + case 96: { + bitField0_ |= 0x00000800; + needsRefresh_ = input.readBool(); + break; + } } } } @@ -4157,6 +4193,27 @@ public final class StorageProtos { return this; } + // optional bool needsRefresh = 12; + private boolean needsRefresh_ ; + public boolean hasNeedsRefresh() { + return ((bitField0_ & 0x00000800) == 0x00000800); + } + public boolean getNeedsRefresh() { + return needsRefresh_; + } + public Builder setNeedsRefresh(boolean value) { + bitField0_ |= 0x00000800; + needsRefresh_ = value; + onChanged(); + return this; + } + public Builder clearNeedsRefresh() { + bitField0_ = (bitField0_ & ~0x00000800); + needsRefresh_ = false; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure) } @@ -4168,6 +4225,703 @@ public final class StorageProtos { // @@protoc_insertion_point(class_scope:textsecure.SessionStructure) } + public interface RecordStructureOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional .textsecure.SessionStructure currentSession = 1; + boolean hasCurrentSession(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getCurrentSession(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getCurrentSessionOrBuilder(); + + // repeated .textsecure.SessionStructure previousSessions = 2; + java.util.List + getPreviousSessionsList(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getPreviousSessions(int index); + int getPreviousSessionsCount(); + java.util.List + getPreviousSessionsOrBuilderList(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getPreviousSessionsOrBuilder( + int index); + } + public static final class RecordStructure extends + com.google.protobuf.GeneratedMessage + implements RecordStructureOrBuilder { + // Use RecordStructure.newBuilder() to construct. + private RecordStructure(Builder builder) { + super(builder); + } + private RecordStructure(boolean noInit) {} + + private static final RecordStructure defaultInstance; + public static RecordStructure getDefaultInstance() { + return defaultInstance; + } + + public RecordStructure getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_fieldAccessorTable; + } + + private int bitField0_; + // optional .textsecure.SessionStructure currentSession = 1; + public static final int CURRENTSESSION_FIELD_NUMBER = 1; + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure currentSession_; + public boolean hasCurrentSession() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getCurrentSession() { + return currentSession_; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getCurrentSessionOrBuilder() { + return currentSession_; + } + + // repeated .textsecure.SessionStructure previousSessions = 2; + public static final int PREVIOUSSESSIONS_FIELD_NUMBER = 2; + private java.util.List previousSessions_; + public java.util.List getPreviousSessionsList() { + return previousSessions_; + } + public java.util.List + getPreviousSessionsOrBuilderList() { + return previousSessions_; + } + public int getPreviousSessionsCount() { + return previousSessions_.size(); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getPreviousSessions(int index) { + return previousSessions_.get(index); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getPreviousSessionsOrBuilder( + int index) { + return previousSessions_.get(index); + } + + private void initFields() { + currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance(); + previousSessions_ = java.util.Collections.emptyList(); + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeMessage(1, currentSession_); + } + for (int i = 0; i < previousSessions_.size(); i++) { + output.writeMessage(2, previousSessions_.get(i)); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, currentSession_); + } + for (int i = 0; i < previousSessions_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, previousSessions_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.storage.StorageProtos.RecordStructure prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.storage.StorageProtos.RecordStructureOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getCurrentSessionFieldBuilder(); + getPreviousSessionsFieldBuilder(); + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + if (currentSessionBuilder_ == null) { + currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance(); + } else { + currentSessionBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + if (previousSessionsBuilder_ == null) { + previousSessions_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + } else { + previousSessionsBuilder_.clear(); + } + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.getDescriptor(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.RecordStructure getDefaultInstanceForType() { + return org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.getDefaultInstance(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.RecordStructure build() { + org.whispersystems.textsecure.storage.StorageProtos.RecordStructure result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.storage.StorageProtos.RecordStructure buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.storage.StorageProtos.RecordStructure result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.storage.StorageProtos.RecordStructure buildPartial() { + org.whispersystems.textsecure.storage.StorageProtos.RecordStructure result = new org.whispersystems.textsecure.storage.StorageProtos.RecordStructure(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + if (currentSessionBuilder_ == null) { + result.currentSession_ = currentSession_; + } else { + result.currentSession_ = currentSessionBuilder_.build(); + } + if (previousSessionsBuilder_ == null) { + if (((bitField0_ & 0x00000002) == 0x00000002)) { + previousSessions_ = java.util.Collections.unmodifiableList(previousSessions_); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.previousSessions_ = previousSessions_; + } else { + result.previousSessions_ = previousSessionsBuilder_.build(); + } + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.storage.StorageProtos.RecordStructure) { + return mergeFrom((org.whispersystems.textsecure.storage.StorageProtos.RecordStructure)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.storage.StorageProtos.RecordStructure other) { + if (other == org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.getDefaultInstance()) return this; + if (other.hasCurrentSession()) { + mergeCurrentSession(other.getCurrentSession()); + } + if (previousSessionsBuilder_ == null) { + if (!other.previousSessions_.isEmpty()) { + if (previousSessions_.isEmpty()) { + previousSessions_ = other.previousSessions_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensurePreviousSessionsIsMutable(); + previousSessions_.addAll(other.previousSessions_); + } + onChanged(); + } + } else { + if (!other.previousSessions_.isEmpty()) { + if (previousSessionsBuilder_.isEmpty()) { + previousSessionsBuilder_.dispose(); + previousSessionsBuilder_ = null; + previousSessions_ = other.previousSessions_; + bitField0_ = (bitField0_ & ~0x00000002); + previousSessionsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getPreviousSessionsFieldBuilder() : null; + } else { + previousSessionsBuilder_.addAllMessages(other.previousSessions_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 10: { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.newBuilder(); + if (hasCurrentSession()) { + subBuilder.mergeFrom(getCurrentSession()); + } + input.readMessage(subBuilder, extensionRegistry); + setCurrentSession(subBuilder.buildPartial()); + break; + } + case 18: { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.newBuilder(); + input.readMessage(subBuilder, extensionRegistry); + addPreviousSessions(subBuilder.buildPartial()); + break; + } + } + } + } + + private int bitField0_; + + // optional .textsecure.SessionStructure currentSession = 1; + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder> currentSessionBuilder_; + public boolean hasCurrentSession() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getCurrentSession() { + if (currentSessionBuilder_ == null) { + return currentSession_; + } else { + return currentSessionBuilder_.getMessage(); + } + } + public Builder setCurrentSession(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) { + if (currentSessionBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + currentSession_ = value; + onChanged(); + } else { + currentSessionBuilder_.setMessage(value); + } + bitField0_ |= 0x00000001; + return this; + } + public Builder setCurrentSession( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) { + if (currentSessionBuilder_ == null) { + currentSession_ = builderForValue.build(); + onChanged(); + } else { + currentSessionBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000001; + return this; + } + public Builder mergeCurrentSession(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) { + if (currentSessionBuilder_ == null) { + if (((bitField0_ & 0x00000001) == 0x00000001) && + currentSession_ != org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance()) { + currentSession_ = + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.newBuilder(currentSession_).mergeFrom(value).buildPartial(); + } else { + currentSession_ = value; + } + onChanged(); + } else { + currentSessionBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000001; + return this; + } + public Builder clearCurrentSession() { + if (currentSessionBuilder_ == null) { + currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance(); + onChanged(); + } else { + currentSessionBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + return this; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder getCurrentSessionBuilder() { + bitField0_ |= 0x00000001; + onChanged(); + return getCurrentSessionFieldBuilder().getBuilder(); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getCurrentSessionOrBuilder() { + if (currentSessionBuilder_ != null) { + return currentSessionBuilder_.getMessageOrBuilder(); + } else { + return currentSession_; + } + } + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder> + getCurrentSessionFieldBuilder() { + if (currentSessionBuilder_ == null) { + currentSessionBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>( + currentSession_, + getParentForChildren(), + isClean()); + currentSession_ = null; + } + return currentSessionBuilder_; + } + + // repeated .textsecure.SessionStructure previousSessions = 2; + private java.util.List previousSessions_ = + java.util.Collections.emptyList(); + private void ensurePreviousSessionsIsMutable() { + if (!((bitField0_ & 0x00000002) == 0x00000002)) { + previousSessions_ = new java.util.ArrayList(previousSessions_); + bitField0_ |= 0x00000002; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder> previousSessionsBuilder_; + + public java.util.List getPreviousSessionsList() { + if (previousSessionsBuilder_ == null) { + return java.util.Collections.unmodifiableList(previousSessions_); + } else { + return previousSessionsBuilder_.getMessageList(); + } + } + public int getPreviousSessionsCount() { + if (previousSessionsBuilder_ == null) { + return previousSessions_.size(); + } else { + return previousSessionsBuilder_.getCount(); + } + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getPreviousSessions(int index) { + if (previousSessionsBuilder_ == null) { + return previousSessions_.get(index); + } else { + return previousSessionsBuilder_.getMessage(index); + } + } + public Builder setPreviousSessions( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) { + if (previousSessionsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePreviousSessionsIsMutable(); + previousSessions_.set(index, value); + onChanged(); + } else { + previousSessionsBuilder_.setMessage(index, value); + } + return this; + } + public Builder setPreviousSessions( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) { + if (previousSessionsBuilder_ == null) { + ensurePreviousSessionsIsMutable(); + previousSessions_.set(index, builderForValue.build()); + onChanged(); + } else { + previousSessionsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + public Builder addPreviousSessions(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) { + if (previousSessionsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePreviousSessionsIsMutable(); + previousSessions_.add(value); + onChanged(); + } else { + previousSessionsBuilder_.addMessage(value); + } + return this; + } + public Builder addPreviousSessions( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) { + if (previousSessionsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePreviousSessionsIsMutable(); + previousSessions_.add(index, value); + onChanged(); + } else { + previousSessionsBuilder_.addMessage(index, value); + } + return this; + } + public Builder addPreviousSessions( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) { + if (previousSessionsBuilder_ == null) { + ensurePreviousSessionsIsMutable(); + previousSessions_.add(builderForValue.build()); + onChanged(); + } else { + previousSessionsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + public Builder addPreviousSessions( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) { + if (previousSessionsBuilder_ == null) { + ensurePreviousSessionsIsMutable(); + previousSessions_.add(index, builderForValue.build()); + onChanged(); + } else { + previousSessionsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + public Builder addAllPreviousSessions( + java.lang.Iterable values) { + if (previousSessionsBuilder_ == null) { + ensurePreviousSessionsIsMutable(); + super.addAll(values, previousSessions_); + onChanged(); + } else { + previousSessionsBuilder_.addAllMessages(values); + } + return this; + } + public Builder clearPreviousSessions() { + if (previousSessionsBuilder_ == null) { + previousSessions_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + } else { + previousSessionsBuilder_.clear(); + } + return this; + } + public Builder removePreviousSessions(int index) { + if (previousSessionsBuilder_ == null) { + ensurePreviousSessionsIsMutable(); + previousSessions_.remove(index); + onChanged(); + } else { + previousSessionsBuilder_.remove(index); + } + return this; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder getPreviousSessionsBuilder( + int index) { + return getPreviousSessionsFieldBuilder().getBuilder(index); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getPreviousSessionsOrBuilder( + int index) { + if (previousSessionsBuilder_ == null) { + return previousSessions_.get(index); } else { + return previousSessionsBuilder_.getMessageOrBuilder(index); + } + } + public java.util.List + getPreviousSessionsOrBuilderList() { + if (previousSessionsBuilder_ != null) { + return previousSessionsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(previousSessions_); + } + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder addPreviousSessionsBuilder() { + return getPreviousSessionsFieldBuilder().addBuilder( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance()); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder addPreviousSessionsBuilder( + int index) { + return getPreviousSessionsFieldBuilder().addBuilder( + index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance()); + } + public java.util.List + getPreviousSessionsBuilderList() { + return getPreviousSessionsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder> + getPreviousSessionsFieldBuilder() { + if (previousSessionsBuilder_ == null) { + previousSessionsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>( + previousSessions_, + ((bitField0_ & 0x00000002) == 0x00000002), + getParentForChildren(), + isClean()); + previousSessions_ = null; + } + return previousSessionsBuilder_; + } + + // @@protoc_insertion_point(builder_scope:textsecure.RecordStructure) + } + + static { + defaultInstance = new RecordStructure(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.RecordStructure) + } + public interface PreKeyRecordStructureOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -4656,6 +5410,11 @@ public final class StorageProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_textsecure_SessionStructure_PendingPreKey_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_RecordStructure_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_RecordStructure_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_textsecure_PreKeyRecordStructure_descriptor; private static @@ -4671,7 +5430,7 @@ public final class StorageProtos { static { java.lang.String[] descriptorData = { "\n\032LocalStorageProtocol.proto\022\ntextsecure" + - "\"\205\010\n\020SessionStructure\022\026\n\016sessionVersion\030" + + "\"\233\010\n\020SessionStructure\022\026\n\016sessionVersion\030" + "\001 \001(\r\022\033\n\023localIdentityPublic\030\002 \001(\014\022\034\n\024re" + "moteIdentityPublic\030\003 \001(\014\022\017\n\007rootKey\030\004 \001(" + "\014\022\027\n\017previousCounter\030\005 \001(\r\0227\n\013senderChai" + @@ -4682,25 +5441,28 @@ public final class StorageProtos { "e.PendingKeyExchange\022A\n\rpendingPreKey\030\t ", "\001(\0132*.textsecure.SessionStructure.Pendin" + "gPreKey\022\034\n\024remoteRegistrationId\030\n \001(\r\022\033\n" + - "\023localRegistrationId\030\013 \001(\r\032\253\002\n\005Chain\022\027\n\017" + - "senderEphemeral\030\001 \001(\014\022\036\n\026senderEphemeral" + - "Private\030\002 \001(\014\022=\n\010chainKey\030\003 \001(\0132+.textse" + - "cure.SessionStructure.Chain.ChainKey\022B\n\013" + - "messageKeys\030\004 \003(\0132-.textsecure.SessionSt" + - "ructure.Chain.MessageKey\032&\n\010ChainKey\022\r\n\005" + - "index\030\001 \001(\r\022\013\n\003key\030\002 \001(\014\032>\n\nMessageKey\022\r" + - "\n\005index\030\001 \001(\r\022\021\n\tcipherKey\030\002 \001(\014\022\016\n\006macK", - "ey\030\003 \001(\014\032\321\001\n\022PendingKeyExchange\022\020\n\010seque" + - "nce\030\001 \001(\r\022\024\n\014localBaseKey\030\002 \001(\014\022\033\n\023local" + - "BaseKeyPrivate\030\003 \001(\014\022\031\n\021localEphemeralKe" + - "y\030\004 \001(\014\022 \n\030localEphemeralKeyPrivate\030\005 \001(" + - "\014\022\030\n\020localIdentityKey\030\007 \001(\014\022\037\n\027localIden" + - "tityKeyPrivate\030\010 \001(\014\0322\n\rPendingPreKey\022\020\n" + - "\010preKeyId\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014\"J\n\025PreK" + - "eyRecordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicK" + - "ey\030\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014B6\n%org.whis" + - "persystems.textsecure.storageB\rStoragePr", - "otos" + "\023localRegistrationId\030\013 \001(\r\022\024\n\014needsRefre" + + "sh\030\014 \001(\010\032\253\002\n\005Chain\022\027\n\017senderEphemeral\030\001 " + + "\001(\014\022\036\n\026senderEphemeralPrivate\030\002 \001(\014\022=\n\010c" + + "hainKey\030\003 \001(\0132+.textsecure.SessionStruct" + + "ure.Chain.ChainKey\022B\n\013messageKeys\030\004 \003(\0132" + + "-.textsecure.SessionStructure.Chain.Mess" + + "ageKey\032&\n\010ChainKey\022\r\n\005index\030\001 \001(\r\022\013\n\003key" + + "\030\002 \001(\014\032>\n\nMessageKey\022\r\n\005index\030\001 \001(\r\022\021\n\tc", + "ipherKey\030\002 \001(\014\022\016\n\006macKey\030\003 \001(\014\032\321\001\n\022Pendi" + + "ngKeyExchange\022\020\n\010sequence\030\001 \001(\r\022\024\n\014local" + + "BaseKey\030\002 \001(\014\022\033\n\023localBaseKeyPrivate\030\003 \001" + + "(\014\022\031\n\021localEphemeralKey\030\004 \001(\014\022 \n\030localEp" + + "hemeralKeyPrivate\030\005 \001(\014\022\030\n\020localIdentity" + + "Key\030\007 \001(\014\022\037\n\027localIdentityKeyPrivate\030\010 \001" + + "(\014\0322\n\rPendingPreKey\022\020\n\010preKeyId\030\001 \001(\r\022\017\n" + + "\007baseKey\030\002 \001(\014\"\177\n\017RecordStructure\0224\n\016cur" + + "rentSession\030\001 \001(\0132\034.textsecure.SessionSt" + + "ructure\0226\n\020previousSessions\030\002 \003(\0132\034.text", + "secure.SessionStructure\"J\n\025PreKeyRecordS" + + "tructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicKey\030\002 \001(\014\022" + + "\022\n\nprivateKey\030\003 \001(\014B6\n%org.whispersystem" + + "s.textsecure.storageB\rStorageProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -4712,7 +5474,7 @@ public final class StorageProtos { internal_static_textsecure_SessionStructure_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_SessionStructure_descriptor, - new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", "RemoteRegistrationId", "LocalRegistrationId", }, + new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", "RemoteRegistrationId", "LocalRegistrationId", "NeedsRefresh", }, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.class, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder.class); internal_static_textsecure_SessionStructure_Chain_descriptor = @@ -4755,8 +5517,16 @@ public final class StorageProtos { new java.lang.String[] { "PreKeyId", "BaseKey", }, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.class, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder.class); - internal_static_textsecure_PreKeyRecordStructure_descriptor = + internal_static_textsecure_RecordStructure_descriptor = getDescriptor().getMessageTypes().get(1); + internal_static_textsecure_RecordStructure_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_RecordStructure_descriptor, + new java.lang.String[] { "CurrentSession", "PreviousSessions", }, + org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.class, + org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.Builder.class); + internal_static_textsecure_PreKeyRecordStructure_descriptor = + getDescriptor().getMessageTypes().get(2); internal_static_textsecure_PreKeyRecordStructure_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_PreKeyRecordStructure_descriptor, diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index 09ba2c146a..7022903ea2 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -420,12 +420,12 @@ public class DecryptingQueue { } } - private void handleKeyExchangeProcessing(String plaintxtBody) { + private void handleKeyExchangeProcessing(String plaintextBody) { if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) { try { Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false).getPrimaryRecipient(); RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId); - KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintxtBody); + KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintextBody); KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, message); if (processor.isStale(message)) { diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java index a7075fa589..e6e697427a 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java @@ -75,7 +75,7 @@ public class KeyExchangeInitiator { RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID); SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipientDevice); - sessionRecordV2.setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey); + sessionRecordV2.getSessionState().setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey); sessionRecordV2.save(); MessageSender.send(context, masterSecret, textMessage, -1); @@ -87,6 +87,7 @@ public class KeyExchangeInitiator { RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID); return new SessionRecordV2(context, masterSecret, recipientDevice) + .getSessionState() .hasPendingKeyExchange(); } diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java index 6fab3c02d3..f1ade383cd 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java @@ -72,8 +72,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { KeyExchangeMessageV2 message = (KeyExchangeMessageV2)m; return message.isResponse() && - (!sessionRecord.hasPendingKeyExchange() || - sessionRecord.getPendingKeyExchangeSequence() != message.getSequence()) && + (!sessionRecord.getSessionState().hasPendingKeyExchange() || + sessionRecord.getSessionState().getPendingKeyExchangeSequence() != message.getSequence()) && !message.isResponseForSimultaneousInitiate(); } @@ -95,18 +95,26 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { if (!PreKeyRecord.hasRecord(context, preKeyId)) throw new InvalidKeyIdException("No such prekey: " + preKeyId); - PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId); - ECKeyPair ourBaseKey = preKeyRecord.getKeyPair(); - ECKeyPair ourEphemeralKey = ourBaseKey; - IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, ourBaseKey.getPublicKey().getType()); + PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId); + ECKeyPair ourBaseKey = preKeyRecord.getKeyPair(); + ECKeyPair ourEphemeralKey = ourBaseKey; + IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, ourBaseKey.getPublicKey().getType()); + boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey(); - sessionRecord.clear(); + if (!simultaneousInitiate) sessionRecord.clear(); + else sessionRecord.archiveCurrentState(); + + RatchetingSession.initializeSession(sessionRecord.getSessionState(), + ourBaseKey, theirBaseKey, + ourEphemeralKey, theirEphemeralKey, + ourIdentityKey, theirIdentityKey); - RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey, - theirEphemeralKey, ourIdentityKey, theirIdentityKey); Session.clearV1SessionFor(context, recipientDevice.getRecipient()); - sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context)); - sessionRecord.setRemoteRegistrationId(message.getRegistrationId()); + sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context)); + sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId()); + + if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true); + sessionRecord.save(); if (preKeyId != Medium.MAX_VALUE) { @@ -129,14 +137,17 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { ourBaseKey.getPublicKey() .getType()); - sessionRecord.clear(); + if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState(); + else sessionRecord.clear(); - RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey, + RatchetingSession.initializeSession(sessionRecord.getSessionState(), + ourBaseKey, theirBaseKey, ourEphemeralKey, theirEphemeralKey, ourIdentityKey, theirIdentityKey); - sessionRecord.setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey()); - sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context)); - sessionRecord.setRemoteRegistrationId(message.getRegistrationId()); + sessionRecord.getSessionState().setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey()); + sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context)); + sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId()); + sessionRecord.save(); DatabaseFactory.getIdentityDatabase(context) @@ -168,23 +179,23 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { Log.w("KeyExchangeProcessorV2", "KeyExchange is an initiate."); - if (!sessionRecord.hasPendingKeyExchange()) { + if (!sessionRecord.getSessionState().hasPendingKeyExchange()) { Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate..."); ourBaseKey = Curve.generateKeyPairForType(message.getBaseKey().getType()); ourEphemeralKey = Curve.generateKeyPairForType(message.getBaseKey().getType()); ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, message.getBaseKey().getType()); - sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey, - ourIdentityKey); + sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey, + ourEphemeralKey, ourIdentityKey); } else { Log.w("KeyExchangeProcessorV2", "We alredy have a pending initiate, responding as simultaneous initiate..."); - ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey(); - ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey(); - ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey(); + ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey(); + ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey(); + ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey(); flags |= KeyExchangeMessageV2.SIMULTAENOUS_INITIATE_FLAG; - sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey, - ourIdentityKey); + sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey, + ourEphemeralKey, ourIdentityKey); } KeyExchangeMessageV2 ourMessage = new KeyExchangeMessageV2(message.getSequence(), @@ -197,23 +208,24 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { MessageSender.send(context, masterSecret, textMessage, threadId); } - if (message.getSequence() != sessionRecord.getPendingKeyExchangeSequence()) { + if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) { Log.w("KeyExchangeProcessorV2", "No matching sequence for response. " + "Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate()); return; } - ECKeyPair ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey(); - ECKeyPair ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey(); - IdentityKeyPair ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey(); + ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey(); + ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey(); + IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey(); sessionRecord.clear(); - RatchetingSession.initializeSession(sessionRecord, ourBaseKey, message.getBaseKey(), + RatchetingSession.initializeSession(sessionRecord.getSessionState(), + ourBaseKey, message.getBaseKey(), ourEphemeralKey, message.getEphemeralKey(), ourIdentityKey, message.getIdentityKey()); - sessionRecord.setSessionVersion(message.getVersion()); + sessionRecord.getSessionState().setSessionVersion(message.getVersion()); Session.clearV1SessionFor(context, recipientDevice.getRecipient()); sessionRecord.save(); diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index f7e41251f8..50155c4dce 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -327,7 +327,9 @@ public class PushTransport extends BaseTransport { PushAddress pushAddress, byte[] plaintext) throws IOException, UntrustedIdentityException { - if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress)) { + if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress) || + SessionRecordV2.needsRefresh(context, masterSecret, pushAddress)) + { try { List preKeys = socket.getPreKeys(pushAddress); diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java index e44a744e20..48c184ce3c 100644 --- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java @@ -33,7 +33,6 @@ import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.RecipientDevice; -import org.whispersystems.textsecure.util.Hex; import java.util.ArrayList;