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;