Handle simultaneous initiate protocol case.

1) Modify SessionRecord to store a list of "previous" sessions
   in addition to the current active session.  Previous sessions
   can be used for receiving messages, but not for sending
   messages.

2) When a possible "simultaneous initiate" is detected, push the
   current session onto the "previous session" stack instead of
   clearing it and starting over.

3) Additionally, mark the new session created on a received
   possible "simultaneous initiate" as stale for sending.  The
   next outgoing message would trigger a full prekey refresh.

4) Work to do: outgoing messages on the SMS transport should
   probably not use the existing session if it's marked stale
   for sending.  These messages need to fail and notify the user,
   similar to how we'll handle SMS fallback to push users before
   a prekey session is created.
This commit is contained in:
Moxie Marlinspike 2014-03-17 17:24:00 -07:00
parent edc20883eb
commit 926d3c929f
12 changed files with 1435 additions and 514 deletions

View File

@ -54,6 +54,13 @@ message SessionStructure {
optional uint32 remoteRegistrationId = 10; optional uint32 remoteRegistrationId = 10;
optional uint32 localRegistrationId = 11; optional uint32 localRegistrationId = 11;
optional bool needsRefresh = 12;
}
message RecordStructure {
optional SessionStructure currentSession = 1;
repeated SessionStructure previousSessions = 2;
} }
message PreKeyRecordStructure { message PreKeyRecordStructure {

View File

@ -1,6 +1,7 @@
package org.whispersystems.textsecure.crypto; package org.whispersystems.textsecure.crypto;
import android.content.Context; import android.content.Context;
import android.util.Log;
import android.util.Pair; import android.util.Pair;
import org.whispersystems.textsecure.crypto.ecc.Curve; 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.crypto.ratchet.RootKey;
import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionRecordV2; import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.SessionState;
import org.whispersystems.textsecure.util.Conversions; import org.whispersystems.textsecure.util.Conversions;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.List;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
@ -45,27 +48,28 @@ public class SessionCipherV2 extends SessionCipher {
public CiphertextMessage encrypt(byte[] paddedMessage) { public CiphertextMessage encrypt(byte[] paddedMessage) {
synchronized (SESSION_LOCK) { synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord(); SessionRecordV2 sessionRecord = getSessionRecord();
ChainKey chainKey = sessionRecord.getSenderChainKey(); SessionState sessionState = sessionRecord.getSessionState();
ChainKey chainKey = sessionState.getSenderChainKey();
MessageKeys messageKeys = chainKey.getMessageKeys(); MessageKeys messageKeys = chainKey.getMessageKeys();
ECPublicKey senderEphemeral = sessionRecord.getSenderEphemeral(); ECPublicKey senderEphemeral = sessionState.getSenderEphemeral();
int previousCounter = sessionRecord.getPreviousCounter(); int previousCounter = sessionState.getPreviousCounter();
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage); byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
CiphertextMessage ciphertextMessage = new WhisperMessageV2(messageKeys.getMacKey(), CiphertextMessage ciphertextMessage = new WhisperMessageV2(messageKeys.getMacKey(),
senderEphemeral, chainKey.getIndex(), senderEphemeral, chainKey.getIndex(),
previousCounter, ciphertextBody); previousCounter, ciphertextBody);
if (sessionRecord.hasPendingPreKey()) { if (sessionState.hasPendingPreKey()) {
Pair<Integer, ECPublicKey> pendingPreKey = sessionRecord.getPendingPreKey(); Pair<Integer, ECPublicKey> pendingPreKey = sessionState.getPendingPreKey();
int localRegistrationId = sessionRecord.getLocalRegistrationId(); int localRegistrationId = sessionState.getLocalRegistrationId();
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first, ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first,
pendingPreKey.second, pendingPreKey.second,
sessionRecord.getLocalIdentityKey(), sessionState.getLocalIdentityKey(),
(WhisperMessageV2) ciphertextMessage); (WhisperMessageV2) ciphertextMessage);
} }
sessionRecord.setSenderChainKey(chainKey.getNextChainKey()); sessionState.setSenderChainKey(chainKey.getNextChainKey());
sessionRecord.save(); sessionRecord.save();
return ciphertextMessage; return ciphertextMessage;
@ -76,49 +80,82 @@ public class SessionCipherV2 extends SessionCipher {
public byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException { public byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException {
synchronized (SESSION_LOCK) { synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord(); SessionRecordV2 sessionRecord = getSessionRecord();
SessionState sessionState = sessionRecord.getSessionState();
List<SessionState> previousStates = sessionRecord.getPreviousSessions();
try {
byte[] plaintext = decrypt(sessionState, decodedMessage);
sessionRecord.save();
return plaintext;
} catch (InvalidMessageException e) {
Log.w("SessionCipherV2", e);
}
for (SessionState previousState : previousStates) {
try {
byte[] plaintext = decrypt(previousState, decodedMessage);
sessionRecord.save();
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); WhisperMessageV2 ciphertextMessage = new WhisperMessageV2(decodedMessage);
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral(); ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter(); int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionRecord, theirEphemeral); ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionRecord, theirEphemeral, MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
chainKey, counter); chainKey, counter);
ciphertextMessage.verifyMac(messageKeys.getMacKey()); ciphertextMessage.verifyMac(messageKeys.getMacKey());
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody()); byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
sessionRecord.clearPendingPreKey(); sessionState.clearPendingPreKey();
sessionRecord.save();
return plaintext; return plaintext;
}
} }
@Override @Override
public int getRemoteRegistrationId() { public int getRemoteRegistrationId() {
synchronized (SESSION_LOCK) { synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord(); 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 throws InvalidMessageException
{ {
try { try {
if (sessionRecord.hasReceiverChain(theirEphemeral)) { if (sessionState.hasReceiverChain(theirEphemeral)) {
return sessionRecord.getReceiverChainKey(theirEphemeral); return sessionState.getReceiverChainKey(theirEphemeral);
} else { } else {
RootKey rootKey = sessionRecord.getRootKey(); RootKey rootKey = sessionState.getRootKey();
ECKeyPair ourEphemeral = sessionRecord.getSenderEphemeralPair(); ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair();
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral); Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
ECKeyPair ourNewEphemeral = Curve.generateKeyPairForType(Curve.DJB_TYPE); ECKeyPair ourNewEphemeral = Curve.generateKeyPairForType(Curve.DJB_TYPE);
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral); Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
sessionRecord.setRootKey(senderChain.first); sessionState.setRootKey(senderChain.first);
sessionRecord.addReceiverChain(theirEphemeral, receiverChain.second); sessionState.addReceiverChain(theirEphemeral, receiverChain.second);
sessionRecord.setPreviousCounter(sessionRecord.getSenderChainKey().getIndex()-1); sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1);
sessionRecord.setSenderChain(ourNewEphemeral, senderChain.second); sessionState.setSenderChain(ourNewEphemeral, senderChain.second);
return receiverChain.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, ECPublicKey theirEphemeral,
ChainKey chainKey, int counter) ChainKey chainKey, int counter)
throws InvalidMessageException throws InvalidMessageException
{ {
if (chainKey.getIndex() > counter) { if (chainKey.getIndex() > counter) {
if (sessionRecord.hasMessageKeys(theirEphemeral, counter)) { if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
return sessionRecord.removeMessageKeys(theirEphemeral, counter); return sessionState.removeMessageKeys(theirEphemeral, counter);
} else { } else {
throw new InvalidMessageException("Received message with old counter!"); throw new InvalidMessageException("Received message with old counter!");
} }
@ -146,11 +183,11 @@ public class SessionCipherV2 extends SessionCipher {
while (chainKey.getIndex() < counter) { while (chainKey.getIndex() < counter) {
MessageKeys messageKeys = chainKey.getMessageKeys(); MessageKeys messageKeys = chainKey.getMessageKeys();
sessionRecord.setMessageKeys(theirEphemeral, messageKeys); sessionState.setMessageKeys(theirEphemeral, messageKeys);
chainKey = chainKey.getNextChainKey(); chainKey = chainKey.getNextChainKey();
} }
sessionRecord.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey()); sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
return chainKey.getMessageKeys(); return chainKey.getMessageKeys();
} }

View File

@ -10,14 +10,14 @@ import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets; import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
import org.whispersystems.textsecure.crypto.kdf.HKDF; 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.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
public class RatchetingSession { public class RatchetingSession {
public static void initializeSession(SessionRecordV2 sessionRecord, public static void initializeSession(SessionState sessionState,
ECKeyPair ourBaseKey, ECKeyPair ourBaseKey,
ECPublicKey theirBaseKey, ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey, ECKeyPair ourEphemeralKey,
@ -27,48 +27,48 @@ public class RatchetingSession {
throws InvalidKeyException throws InvalidKeyException
{ {
if (isAlice(ourBaseKey.getPublicKey(), theirBaseKey, ourEphemeralKey.getPublicKey(), theirEphemeralKey)) { if (isAlice(ourBaseKey.getPublicKey(), theirBaseKey, ourEphemeralKey.getPublicKey(), theirEphemeralKey)) {
initializeSessionAsAlice(sessionRecord, ourBaseKey, theirBaseKey, theirEphemeralKey, initializeSessionAsAlice(sessionState, ourBaseKey, theirBaseKey, theirEphemeralKey,
ourIdentityKey, theirIdentityKey); ourIdentityKey, theirIdentityKey);
} else { } else {
initializeSessionAsBob(sessionRecord, ourBaseKey, theirBaseKey, initializeSessionAsBob(sessionState, ourBaseKey, theirBaseKey,
ourEphemeralKey, ourIdentityKey, theirIdentityKey); ourEphemeralKey, ourIdentityKey, theirIdentityKey);
} }
} }
private static void initializeSessionAsAlice(SessionRecordV2 sessionRecord, private static void initializeSessionAsAlice(SessionState sessionState,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey, ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECPublicKey theirEphemeralKey, ECPublicKey theirEphemeralKey,
IdentityKeyPair ourIdentityKey, IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey) IdentityKey theirIdentityKey)
throws InvalidKeyException throws InvalidKeyException
{ {
sessionRecord.setRemoteIdentityKey(theirIdentityKey); sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionRecord.setLocalIdentityKey(ourIdentityKey.getPublicKey()); sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
ECKeyPair sendingKey = Curve.generateKeyPairForType(ourIdentityKey.getPublicKey().getPublicKey().getType()); ECKeyPair sendingKey = Curve.generateKeyPairForType(ourIdentityKey.getPublicKey().getPublicKey().getType());
Pair<RootKey, ChainKey> receivingChain = calculate3DHE(true, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey); Pair<RootKey, ChainKey> receivingChain = calculate3DHE(true, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey);
Pair<RootKey, ChainKey> sendingChain = receivingChain.first.createChain(theirEphemeralKey, sendingKey); Pair<RootKey, ChainKey> sendingChain = receivingChain.first.createChain(theirEphemeralKey, sendingKey);
sessionRecord.addReceiverChain(theirEphemeralKey, receivingChain.second); sessionState.addReceiverChain(theirEphemeralKey, receivingChain.second);
sessionRecord.setSenderChain(sendingKey, sendingChain.second); sessionState.setSenderChain(sendingKey, sendingChain.second);
sessionRecord.setRootKey(sendingChain.first); sessionState.setRootKey(sendingChain.first);
} }
private static void initializeSessionAsBob(SessionRecordV2 sessionRecord, private static void initializeSessionAsBob(SessionState sessionState,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey, ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey, ECKeyPair ourEphemeralKey,
IdentityKeyPair ourIdentityKey, IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey) IdentityKey theirIdentityKey)
throws InvalidKeyException throws InvalidKeyException
{ {
sessionRecord.setRemoteIdentityKey(theirIdentityKey); sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionRecord.setLocalIdentityKey(ourIdentityKey.getPublicKey()); sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
Pair<RootKey, ChainKey> sendingChain = calculate3DHE(false, ourBaseKey, theirBaseKey, Pair<RootKey, ChainKey> sendingChain = calculate3DHE(false, ourBaseKey, theirBaseKey,
ourIdentityKey, theirIdentityKey); ourIdentityKey, theirIdentityKey);
sessionRecord.setSenderChain(ourEphemeralKey, sendingChain.second); sessionState.setSenderChain(ourEphemeralKey, sendingChain.second);
sessionRecord.setRootKey(sendingChain.first); sessionState.setRootKey(sendingChain.first);
} }
private static Pair<RootKey, ChainKey> calculate3DHE(boolean isAlice, private static Pair<RootKey, ChainKey> calculate3DHE(boolean isAlice,

View File

@ -49,7 +49,6 @@ public class Session {
return SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(), return SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID); RecipientDevice.DEFAULT_DEVICE_ID);
} }
private static boolean hasV1Session(Context context, CanonicalRecipient recipient) { private static boolean hasV1Session(Context context, CanonicalRecipient recipient) {
return SessionRecordV1.hasSession(context, recipient) && return SessionRecordV1.hasSession(context, recipient) &&
RemoteKeyRecord.hasRecord(context, recipient) && RemoteKeyRecord.hasRecord(context, recipient) &&
@ -70,7 +69,8 @@ public class Session {
RecipientDevice.DEFAULT_DEVICE_ID)) RecipientDevice.DEFAULT_DEVICE_ID))
{ {
return new SessionRecordV2(context, masterSecret, recipientId, return new SessionRecordV2(context, masterSecret, recipientId,
RecipientDevice.DEFAULT_DEVICE_ID).getRemoteIdentityKey(); RecipientDevice.DEFAULT_DEVICE_ID).getSessionState()
.getRemoteIdentityKey();
} else if (SessionRecordV1.hasSession(context, recipientId)) { } else if (SessionRecordV1.hasSession(context, recipientId)) {
return new SessionRecordV1(context, masterSecret, recipientId).getIdentityKey(); return new SessionRecordV1(context, masterSecret, recipientId).getIdentityKey();
} else { } else {

View File

@ -18,26 +18,10 @@ package org.whispersystems.textsecure.storage;
import android.content.Context; import android.content.Context;
import android.util.Log; 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.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret; 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.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -45,11 +29,11 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; 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. * A disk record representing a current session.
@ -60,11 +44,15 @@ import javax.crypto.spec.SecretKeySpec;
public class SessionRecordV2 extends Record { public class SessionRecordV2 extends Record {
private static final Object FILE_LOCK = new Object(); 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 final MasterSecret masterSecret;
private StorageProtos.SessionStructure sessionStructure =
StorageProtos.SessionStructure.newBuilder().build(); private SessionState sessionState = new SessionState(SessionStructure.newBuilder().build());
private List<SessionState> previousStates = new LinkedList<SessionState>();
public SessionRecordV2(Context context, MasterSecret masterSecret, RecipientDevice peer) { public SessionRecordV2(Context context, MasterSecret masterSecret, RecipientDevice peer) {
this(context, masterSecret, peer.getRecipientId(), peer.getDeviceId()); this(context, masterSecret, peer.getRecipientId(), peer.getDeviceId());
@ -80,6 +68,15 @@ public class SessionRecordV2 extends Record {
return recipientId + (deviceId == RecipientDevice.DEFAULT_DEVICE_ID ? "" : "." + deviceId); return recipientId + (deviceId == RecipientDevice.DEFAULT_DEVICE_ID ? "" : "." + deviceId);
} }
public SessionState getSessionState() {
return sessionState;
}
public List<SessionState> getPreviousSessions() {
return previousStates;
}
public static List<Integer> getSessionSubDevices(Context context, CanonicalRecipient recipient) { public static List<Integer> getSessionSubDevices(Context context, CanonicalRecipient recipient) {
List<Integer> results = new LinkedList<Integer>(); List<Integer> results = new LinkedList<Integer>();
File parent = getParentDirectory(context, SESSIONS_DIRECTORY_V2); File parent = getParentDirectory(context, SESSIONS_DIRECTORY_V2);
@ -129,404 +126,49 @@ public class SessionRecordV2 extends Record {
long recipientId, int deviceId) long recipientId, int deviceId)
{ {
return hasRecord(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientId, 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() { public void clear() {
this.sessionStructure = StorageProtos.SessionStructure.newBuilder().build(); this.sessionState = new SessionState(SessionStructure.newBuilder().build());
this.previousStates = new LinkedList<SessionState>();
} }
public void setSessionVersion(int version) { public void archiveCurrentState() {
this.sessionStructure = this.sessionStructure.toBuilder() this.previousStates.add(sessionState);
.setSessionVersion(version) this.sessionState = new SessionState(SessionStructure.newBuilder().build());
.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<Chain,Integer> getReceiverChain(ECPublicKey senderEphemeral) {
List<Chain> 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<Chain,Integer>(receiverChain,index);
}
} catch (InvalidKeyException e) {
Log.w("SessionRecordV2", e);
}
index++;
}
return null;
}
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
Pair<Chain,Integer> 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<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return false;
}
List<Chain.MessageKey> messageKeyList = chain.getMessageKeysList();
for (Chain.MessageKey messageKey : messageKeyList) {
if (messageKey.getIndex() == counter) {
return true;
}
}
return false;
}
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return null;
}
List<Chain.MessageKey> messageKeyList = new LinkedList<Chain.MessageKey>(chain.getMessageKeysList());
Iterator<Chain.MessageKey> 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<Chain,Integer> 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<Chain,Integer> 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<Integer, ECPublicKey> getPendingPreKey() {
try {
return new Pair<Integer, ECPublicKey>(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 save() { public void save() {
synchronized (FILE_LOCK) { synchronized (FILE_LOCK) {
try { try {
List<SessionStructure> previousStructures = new LinkedList<SessionStructure>();
for (SessionState previousState : previousStates) {
previousStructures.add(previousState.getStructure());
}
RecordStructure record = RecordStructure.newBuilder()
.setCurrentSession(sessionState.getStructure())
.addAllPreviousSessions(previousStructures)
.build();
RandomAccessFile file = openRandomAccessFile(); RandomAccessFile file = openRandomAccessFile();
FileChannel out = file.getChannel(); FileChannel out = file.getChannel();
out.position(0); out.position(0);
MasterCipher cipher = new MasterCipher(masterSecret); MasterCipher cipher = new MasterCipher(masterSecret);
writeInteger(CURRENT_VERSION, out); writeInteger(CURRENT_VERSION, out);
writeBlob(cipher.encryptBytes(sessionStructure.toByteArray()), out); writeBlob(cipher.encryptBytes(record.toByteArray()), out);
out.truncate(out.position()); out.truncate(out.position());
file.close(); file.close();
@ -549,11 +191,26 @@ public class SessionRecordV2 extends Record {
MasterCipher cipher = new MasterCipher(masterSecret); MasterCipher cipher = new MasterCipher(masterSecret);
byte[] encryptedBlob = readBlob(in); 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 this.sessionState = new SessionState(recordStructure.getCurrentSession());
.parseFrom(cipher.decryptBytes(encryptedBlob)); this.previousStates = new LinkedList<SessionState>();
for (SessionStructure sessionStructure : recordStructure.getPreviousSessionsList()) {
this.previousStates.add(new SessionState(sessionStructure));
}
} else {
throw new AssertionError("Unknown version: " + versionMarker);
}
in.close(); in.close();
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Log.w("SessionRecordV2", "No session information found."); Log.w("SessionRecordV2", "No session information found.");
// XXX // XXX

View File

@ -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<Chain,Integer> getReceiverChain(ECPublicKey senderEphemeral) {
List<Chain> 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<Chain,Integer>(receiverChain,index);
}
} catch (InvalidKeyException e) {
Log.w("SessionRecordV2", e);
}
index++;
}
return null;
}
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
Pair<Chain,Integer> 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<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return false;
}
List<Chain.MessageKey> messageKeyList = chain.getMessageKeysList();
for (Chain.MessageKey messageKey : messageKeyList) {
if (messageKey.getIndex() == counter) {
return true;
}
}
return false;
}
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return null;
}
List<Chain.MessageKey> messageKeyList = new LinkedList<Chain.MessageKey>(chain.getMessageKeysList());
Iterator<Chain.MessageKey> 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<Chain,Integer> 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<Chain,Integer> 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<Integer, ECPublicKey> getPendingPreKey() {
try {
return new Pair<Integer, ECPublicKey>(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();
}
}

View File

@ -63,6 +63,10 @@ public final class StorageProtos {
// optional uint32 localRegistrationId = 11; // optional uint32 localRegistrationId = 11;
boolean hasLocalRegistrationId(); boolean hasLocalRegistrationId();
int getLocalRegistrationId(); int getLocalRegistrationId();
// optional bool needsRefresh = 12;
boolean hasNeedsRefresh();
boolean getNeedsRefresh();
} }
public static final class SessionStructure extends public static final class SessionStructure extends
com.google.protobuf.GeneratedMessage com.google.protobuf.GeneratedMessage
@ -2992,6 +2996,16 @@ public final class StorageProtos {
return localRegistrationId_; 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() { private void initFields() {
sessionVersion_ = 0; sessionVersion_ = 0;
localIdentityPublic_ = com.google.protobuf.ByteString.EMPTY; localIdentityPublic_ = com.google.protobuf.ByteString.EMPTY;
@ -3004,6 +3018,7 @@ public final class StorageProtos {
pendingPreKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance(); pendingPreKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance();
remoteRegistrationId_ = 0; remoteRegistrationId_ = 0;
localRegistrationId_ = 0; localRegistrationId_ = 0;
needsRefresh_ = false;
} }
private byte memoizedIsInitialized = -1; private byte memoizedIsInitialized = -1;
public final boolean isInitialized() { public final boolean isInitialized() {
@ -3050,6 +3065,9 @@ public final class StorageProtos {
if (((bitField0_ & 0x00000200) == 0x00000200)) { if (((bitField0_ & 0x00000200) == 0x00000200)) {
output.writeUInt32(11, localRegistrationId_); output.writeUInt32(11, localRegistrationId_);
} }
if (((bitField0_ & 0x00000400) == 0x00000400)) {
output.writeBool(12, needsRefresh_);
}
getUnknownFields().writeTo(output); getUnknownFields().writeTo(output);
} }
@ -3103,6 +3121,10 @@ public final class StorageProtos {
size += com.google.protobuf.CodedOutputStream size += com.google.protobuf.CodedOutputStream
.computeUInt32Size(11, localRegistrationId_); .computeUInt32Size(11, localRegistrationId_);
} }
if (((bitField0_ & 0x00000400) == 0x00000400)) {
size += com.google.protobuf.CodedOutputStream
.computeBoolSize(12, needsRefresh_);
}
size += getUnknownFields().getSerializedSize(); size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size; memoizedSerializedSize = size;
return size; return size;
@ -3269,6 +3291,8 @@ public final class StorageProtos {
bitField0_ = (bitField0_ & ~0x00000200); bitField0_ = (bitField0_ & ~0x00000200);
localRegistrationId_ = 0; localRegistrationId_ = 0;
bitField0_ = (bitField0_ & ~0x00000400); bitField0_ = (bitField0_ & ~0x00000400);
needsRefresh_ = false;
bitField0_ = (bitField0_ & ~0x00000800);
return this; return this;
} }
@ -3368,6 +3392,10 @@ public final class StorageProtos {
to_bitField0_ |= 0x00000200; to_bitField0_ |= 0x00000200;
} }
result.localRegistrationId_ = localRegistrationId_; result.localRegistrationId_ = localRegistrationId_;
if (((from_bitField0_ & 0x00000800) == 0x00000800)) {
to_bitField0_ |= 0x00000400;
}
result.needsRefresh_ = needsRefresh_;
result.bitField0_ = to_bitField0_; result.bitField0_ = to_bitField0_;
onBuilt(); onBuilt();
return result; return result;
@ -3440,6 +3468,9 @@ public final class StorageProtos {
if (other.hasLocalRegistrationId()) { if (other.hasLocalRegistrationId()) {
setLocalRegistrationId(other.getLocalRegistrationId()); setLocalRegistrationId(other.getLocalRegistrationId());
} }
if (other.hasNeedsRefresh()) {
setNeedsRefresh(other.getNeedsRefresh());
}
this.mergeUnknownFields(other.getUnknownFields()); this.mergeUnknownFields(other.getUnknownFields());
return this; return this;
} }
@ -3539,6 +3570,11 @@ public final class StorageProtos {
localRegistrationId_ = input.readUInt32(); localRegistrationId_ = input.readUInt32();
break; break;
} }
case 96: {
bitField0_ |= 0x00000800;
needsRefresh_ = input.readBool();
break;
}
} }
} }
} }
@ -4157,6 +4193,27 @@ public final class StorageProtos {
return this; 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) // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure)
} }
@ -4168,6 +4225,703 @@ public final class StorageProtos {
// @@protoc_insertion_point(class_scope:textsecure.SessionStructure) // @@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<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure>
getPreviousSessionsList();
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getPreviousSessions(int index);
int getPreviousSessionsCount();
java.util.List<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
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<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> previousSessions_;
public java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> getPreviousSessionsList() {
return previousSessions_;
}
public java.util.List<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
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<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<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> previousSessions_ =
java.util.Collections.emptyList();
private void ensurePreviousSessionsIsMutable() {
if (!((bitField0_ & 0x00000002) == 0x00000002)) {
previousSessions_ = new java.util.ArrayList<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure>(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<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> 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<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> 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<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
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<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder>
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 public interface PreKeyRecordStructureOrBuilder
extends com.google.protobuf.MessageOrBuilder { extends com.google.protobuf.MessageOrBuilder {
@ -4656,6 +5410,11 @@ public final class StorageProtos {
private static private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_textsecure_SessionStructure_PendingPreKey_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 private static com.google.protobuf.Descriptors.Descriptor
internal_static_textsecure_PreKeyRecordStructure_descriptor; internal_static_textsecure_PreKeyRecordStructure_descriptor;
private static private static
@ -4671,7 +5430,7 @@ public final class StorageProtos {
static { static {
java.lang.String[] descriptorData = { java.lang.String[] descriptorData = {
"\n\032LocalStorageProtocol.proto\022\ntextsecure" + "\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" + "\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(" + "moteIdentityPublic\030\003 \001(\014\022\017\n\007rootKey\030\004 \001(" +
"\014\022\027\n\017previousCounter\030\005 \001(\r\0227\n\013senderChai" + "\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 ", "e.PendingKeyExchange\022A\n\rpendingPreKey\030\t ",
"\001(\0132*.textsecure.SessionStructure.Pendin" + "\001(\0132*.textsecure.SessionStructure.Pendin" +
"gPreKey\022\034\n\024remoteRegistrationId\030\n \001(\r\022\033\n" + "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" + "\023localRegistrationId\030\013 \001(\r\022\024\n\014needsRefre" +
"senderEphemeral\030\001 \001(\014\022\036\n\026senderEphemeral" + "sh\030\014 \001(\010\032\253\002\n\005Chain\022\027\n\017senderEphemeral\030\001 " +
"Private\030\002 \001(\014\022=\n\010chainKey\030\003 \001(\0132+.textse" + "\001(\014\022\036\n\026senderEphemeralPrivate\030\002 \001(\014\022=\n\010c" +
"cure.SessionStructure.Chain.ChainKey\022B\n\013" + "hainKey\030\003 \001(\0132+.textsecure.SessionStruct" +
"messageKeys\030\004 \003(\0132-.textsecure.SessionSt" + "ure.Chain.ChainKey\022B\n\013messageKeys\030\004 \003(\0132" +
"ructure.Chain.MessageKey\032&\n\010ChainKey\022\r\n\005" + "-.textsecure.SessionStructure.Chain.Mess" +
"index\030\001 \001(\r\022\013\n\003key\030\002 \001(\014\032>\n\nMessageKey\022\r" + "ageKey\032&\n\010ChainKey\022\r\n\005index\030\001 \001(\r\022\013\n\003key" +
"\n\005index\030\001 \001(\r\022\021\n\tcipherKey\030\002 \001(\014\022\016\n\006macK", "\030\002 \001(\014\032>\n\nMessageKey\022\r\n\005index\030\001 \001(\r\022\021\n\tc",
"ey\030\003 \001(\014\032\321\001\n\022PendingKeyExchange\022\020\n\010seque" + "ipherKey\030\002 \001(\014\022\016\n\006macKey\030\003 \001(\014\032\321\001\n\022Pendi" +
"nce\030\001 \001(\r\022\024\n\014localBaseKey\030\002 \001(\014\022\033\n\023local" + "ngKeyExchange\022\020\n\010sequence\030\001 \001(\r\022\024\n\014local" +
"BaseKeyPrivate\030\003 \001(\014\022\031\n\021localEphemeralKe" + "BaseKey\030\002 \001(\014\022\033\n\023localBaseKeyPrivate\030\003 \001" +
"y\030\004 \001(\014\022 \n\030localEphemeralKeyPrivate\030\005 \001(" + "(\014\022\031\n\021localEphemeralKey\030\004 \001(\014\022 \n\030localEp" +
"\014\022\030\n\020localIdentityKey\030\007 \001(\014\022\037\n\027localIden" + "hemeralKeyPrivate\030\005 \001(\014\022\030\n\020localIdentity" +
"tityKeyPrivate\030\010 \001(\014\0322\n\rPendingPreKey\022\020\n" + "Key\030\007 \001(\014\022\037\n\027localIdentityKeyPrivate\030\010 \001" +
"\010preKeyId\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014\"J\n\025PreK" + "(\014\0322\n\rPendingPreKey\022\020\n\010preKeyId\030\001 \001(\r\022\017\n" +
"eyRecordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicK" + "\007baseKey\030\002 \001(\014\"\177\n\017RecordStructure\0224\n\016cur" +
"ey\030\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014B6\n%org.whis" + "rentSession\030\001 \001(\0132\034.textsecure.SessionSt" +
"persystems.textsecure.storageB\rStoragePr", "ructure\0226\n\020previousSessions\030\002 \003(\0132\034.text",
"otos" "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 = com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@ -4712,7 +5474,7 @@ public final class StorageProtos {
internal_static_textsecure_SessionStructure_fieldAccessorTable = new internal_static_textsecure_SessionStructure_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable( com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_SessionStructure_descriptor, 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.class,
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder.class); org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder.class);
internal_static_textsecure_SessionStructure_Chain_descriptor = internal_static_textsecure_SessionStructure_Chain_descriptor =
@ -4755,8 +5517,16 @@ public final class StorageProtos {
new java.lang.String[] { "PreKeyId", "BaseKey", }, new java.lang.String[] { "PreKeyId", "BaseKey", },
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.class, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.class,
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder.class); org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder.class);
internal_static_textsecure_PreKeyRecordStructure_descriptor = internal_static_textsecure_RecordStructure_descriptor =
getDescriptor().getMessageTypes().get(1); 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 internal_static_textsecure_PreKeyRecordStructure_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable( com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_PreKeyRecordStructure_descriptor, internal_static_textsecure_PreKeyRecordStructure_descriptor,

View File

@ -420,12 +420,12 @@ public class DecryptingQueue {
} }
} }
private void handleKeyExchangeProcessing(String plaintxtBody) { private void handleKeyExchangeProcessing(String plaintextBody) {
if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) { if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
try { try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false).getPrimaryRecipient(); Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId); 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); KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, message);
if (processor.isStale(message)) { if (processor.isStale(message)) {

View File

@ -75,7 +75,7 @@ public class KeyExchangeInitiator {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID); RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipientDevice); SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipientDevice);
sessionRecordV2.setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey); sessionRecordV2.getSessionState().setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
sessionRecordV2.save(); sessionRecordV2.save();
MessageSender.send(context, masterSecret, textMessage, -1); MessageSender.send(context, masterSecret, textMessage, -1);
@ -87,6 +87,7 @@ public class KeyExchangeInitiator {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID); RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
return return
new SessionRecordV2(context, masterSecret, recipientDevice) new SessionRecordV2(context, masterSecret, recipientDevice)
.getSessionState()
.hasPendingKeyExchange(); .hasPendingKeyExchange();
} }

View File

@ -72,8 +72,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)m; KeyExchangeMessageV2 message = (KeyExchangeMessageV2)m;
return return
message.isResponse() && message.isResponse() &&
(!sessionRecord.hasPendingKeyExchange() || (!sessionRecord.getSessionState().hasPendingKeyExchange() ||
sessionRecord.getPendingKeyExchangeSequence() != message.getSequence()) && sessionRecord.getSessionState().getPendingKeyExchangeSequence() != message.getSequence()) &&
!message.isResponseForSimultaneousInitiate(); !message.isResponseForSimultaneousInitiate();
} }
@ -99,14 +99,22 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair(); ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
ECKeyPair ourEphemeralKey = ourBaseKey; ECKeyPair ourEphemeralKey = ourBaseKey;
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, ourBaseKey.getPublicKey().getType()); 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()); Session.clearV1SessionFor(context, recipientDevice.getRecipient());
sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context)); sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.setRemoteRegistrationId(message.getRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
sessionRecord.save(); sessionRecord.save();
if (preKeyId != Medium.MAX_VALUE) { if (preKeyId != Medium.MAX_VALUE) {
@ -129,14 +137,17 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
ourBaseKey.getPublicKey() ourBaseKey.getPublicKey()
.getType()); .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); theirEphemeralKey, ourIdentityKey, theirIdentityKey);
sessionRecord.setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey()); sessionRecord.getSessionState().setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context)); sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.setRemoteRegistrationId(message.getRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
sessionRecord.save(); sessionRecord.save();
DatabaseFactory.getIdentityDatabase(context) DatabaseFactory.getIdentityDatabase(context)
@ -168,23 +179,23 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
Log.w("KeyExchangeProcessorV2", "KeyExchange is an initiate."); Log.w("KeyExchangeProcessorV2", "KeyExchange is an initiate.");
if (!sessionRecord.hasPendingKeyExchange()) { if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate..."); Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate...");
ourBaseKey = Curve.generateKeyPairForType(message.getBaseKey().getType()); ourBaseKey = Curve.generateKeyPairForType(message.getBaseKey().getType());
ourEphemeralKey = Curve.generateKeyPairForType(message.getBaseKey().getType()); ourEphemeralKey = Curve.generateKeyPairForType(message.getBaseKey().getType());
ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, message.getBaseKey().getType()); ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, message.getBaseKey().getType());
sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey, sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourIdentityKey); ourEphemeralKey, ourIdentityKey);
} else { } else {
Log.w("KeyExchangeProcessorV2", "We alredy have a pending initiate, responding as simultaneous initiate..."); Log.w("KeyExchangeProcessorV2", "We alredy have a pending initiate, responding as simultaneous initiate...");
ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey(); ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey(); ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey(); ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
flags |= KeyExchangeMessageV2.SIMULTAENOUS_INITIATE_FLAG; flags |= KeyExchangeMessageV2.SIMULTAENOUS_INITIATE_FLAG;
sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey, sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourIdentityKey); ourEphemeralKey, ourIdentityKey);
} }
KeyExchangeMessageV2 ourMessage = new KeyExchangeMessageV2(message.getSequence(), KeyExchangeMessageV2 ourMessage = new KeyExchangeMessageV2(message.getSequence(),
@ -197,23 +208,24 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
MessageSender.send(context, masterSecret, textMessage, threadId); 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. " + Log.w("KeyExchangeProcessorV2", "No matching sequence for response. " +
"Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate()); "Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
return; return;
} }
ECKeyPair ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey(); ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ECKeyPair ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey(); ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
IdentityKeyPair ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey(); IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
sessionRecord.clear(); sessionRecord.clear();
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, message.getBaseKey(), RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, message.getBaseKey(),
ourEphemeralKey, message.getEphemeralKey(), ourEphemeralKey, message.getEphemeralKey(),
ourIdentityKey, message.getIdentityKey()); ourIdentityKey, message.getIdentityKey());
sessionRecord.setSessionVersion(message.getVersion()); sessionRecord.getSessionState().setSessionVersion(message.getVersion());
Session.clearV1SessionFor(context, recipientDevice.getRecipient()); Session.clearV1SessionFor(context, recipientDevice.getRecipient());
sessionRecord.save(); sessionRecord.save();

View File

@ -327,7 +327,9 @@ public class PushTransport extends BaseTransport {
PushAddress pushAddress, byte[] plaintext) PushAddress pushAddress, byte[] plaintext)
throws IOException, UntrustedIdentityException throws IOException, UntrustedIdentityException
{ {
if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress)) { if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress) ||
SessionRecordV2.needsRefresh(context, masterSecret, pushAddress))
{
try { try {
List<PreKeyEntity> preKeys = socket.getPreKeys(pushAddress); List<PreKeyEntity> preKeys = socket.getPreKeys(pushAddress);

View File

@ -33,7 +33,6 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Hex;
import java.util.ArrayList; import java.util.ArrayList;