mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-22 13:57:31 +00:00
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:
@@ -420,12 +420,12 @@ public class DecryptingQueue {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleKeyExchangeProcessing(String plaintxtBody) {
|
||||
private void handleKeyExchangeProcessing(String plaintextBody) {
|
||||
if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
|
||||
try {
|
||||
Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false).getPrimaryRecipient();
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId);
|
||||
KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintxtBody);
|
||||
KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintextBody);
|
||||
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, message);
|
||||
|
||||
if (processor.isStale(message)) {
|
||||
|
@@ -75,7 +75,7 @@ public class KeyExchangeInitiator {
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
|
||||
SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipientDevice);
|
||||
sessionRecordV2.setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
|
||||
sessionRecordV2.getSessionState().setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
|
||||
sessionRecordV2.save();
|
||||
|
||||
MessageSender.send(context, masterSecret, textMessage, -1);
|
||||
@@ -87,6 +87,7 @@ public class KeyExchangeInitiator {
|
||||
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
return
|
||||
new SessionRecordV2(context, masterSecret, recipientDevice)
|
||||
.getSessionState()
|
||||
.hasPendingKeyExchange();
|
||||
}
|
||||
|
||||
|
@@ -72,8 +72,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)m;
|
||||
return
|
||||
message.isResponse() &&
|
||||
(!sessionRecord.hasPendingKeyExchange() ||
|
||||
sessionRecord.getPendingKeyExchangeSequence() != message.getSequence()) &&
|
||||
(!sessionRecord.getSessionState().hasPendingKeyExchange() ||
|
||||
sessionRecord.getSessionState().getPendingKeyExchangeSequence() != message.getSequence()) &&
|
||||
!message.isResponseForSimultaneousInitiate();
|
||||
}
|
||||
|
||||
@@ -95,18 +95,26 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
if (!PreKeyRecord.hasRecord(context, preKeyId))
|
||||
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
|
||||
|
||||
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
|
||||
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
|
||||
ECKeyPair ourEphemeralKey = ourBaseKey;
|
||||
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, ourBaseKey.getPublicKey().getType());
|
||||
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
|
||||
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
|
||||
ECKeyPair ourEphemeralKey = ourBaseKey;
|
||||
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, ourBaseKey.getPublicKey().getType());
|
||||
boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
|
||||
|
||||
sessionRecord.clear();
|
||||
if (!simultaneousInitiate) sessionRecord.clear();
|
||||
else sessionRecord.archiveCurrentState();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
||||
ourBaseKey, theirBaseKey,
|
||||
ourEphemeralKey, theirEphemeralKey,
|
||||
ourIdentityKey, theirIdentityKey);
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey,
|
||||
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
|
||||
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
|
||||
sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
|
||||
sessionRecord.setRemoteRegistrationId(message.getRegistrationId());
|
||||
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
|
||||
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
|
||||
|
||||
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
|
||||
|
||||
sessionRecord.save();
|
||||
|
||||
if (preKeyId != Medium.MAX_VALUE) {
|
||||
@@ -129,14 +137,17 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
ourBaseKey.getPublicKey()
|
||||
.getType());
|
||||
|
||||
sessionRecord.clear();
|
||||
if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
|
||||
else sessionRecord.clear();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey,
|
||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
||||
ourBaseKey, theirBaseKey, ourEphemeralKey,
|
||||
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
|
||||
|
||||
sessionRecord.setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
|
||||
sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
|
||||
sessionRecord.setRemoteRegistrationId(message.getRegistrationId());
|
||||
sessionRecord.getSessionState().setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
|
||||
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
|
||||
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
|
||||
|
||||
sessionRecord.save();
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
@@ -168,23 +179,23 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
|
||||
Log.w("KeyExchangeProcessorV2", "KeyExchange is an initiate.");
|
||||
|
||||
if (!sessionRecord.hasPendingKeyExchange()) {
|
||||
if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
|
||||
Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate...");
|
||||
ourBaseKey = Curve.generateKeyPairForType(message.getBaseKey().getType());
|
||||
ourEphemeralKey = Curve.generateKeyPairForType(message.getBaseKey().getType());
|
||||
ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, message.getBaseKey().getType());
|
||||
|
||||
sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey,
|
||||
ourIdentityKey);
|
||||
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
|
||||
ourEphemeralKey, ourIdentityKey);
|
||||
} else {
|
||||
Log.w("KeyExchangeProcessorV2", "We alredy have a pending initiate, responding as simultaneous initiate...");
|
||||
ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey();
|
||||
ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey();
|
||||
ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey();
|
||||
ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
|
||||
ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
|
||||
ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
|
||||
flags |= KeyExchangeMessageV2.SIMULTAENOUS_INITIATE_FLAG;
|
||||
|
||||
sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey,
|
||||
ourIdentityKey);
|
||||
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
|
||||
ourEphemeralKey, ourIdentityKey);
|
||||
}
|
||||
|
||||
KeyExchangeMessageV2 ourMessage = new KeyExchangeMessageV2(message.getSequence(),
|
||||
@@ -197,23 +208,24 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
MessageSender.send(context, masterSecret, textMessage, threadId);
|
||||
}
|
||||
|
||||
if (message.getSequence() != sessionRecord.getPendingKeyExchangeSequence()) {
|
||||
if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) {
|
||||
Log.w("KeyExchangeProcessorV2", "No matching sequence for response. " +
|
||||
"Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
|
||||
return;
|
||||
}
|
||||
|
||||
ECKeyPair ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey();
|
||||
ECKeyPair ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey();
|
||||
IdentityKeyPair ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey();
|
||||
ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
|
||||
ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
|
||||
IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
|
||||
|
||||
sessionRecord.clear();
|
||||
|
||||
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, message.getBaseKey(),
|
||||
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
||||
ourBaseKey, message.getBaseKey(),
|
||||
ourEphemeralKey, message.getEphemeralKey(),
|
||||
ourIdentityKey, message.getIdentityKey());
|
||||
|
||||
sessionRecord.setSessionVersion(message.getVersion());
|
||||
sessionRecord.getSessionState().setSessionVersion(message.getVersion());
|
||||
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
|
||||
sessionRecord.save();
|
||||
|
||||
|
@@ -327,7 +327,9 @@ public class PushTransport extends BaseTransport {
|
||||
PushAddress pushAddress, byte[] plaintext)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress)) {
|
||||
if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress) ||
|
||||
SessionRecordV2.needsRefresh(context, masterSecret, pushAddress))
|
||||
{
|
||||
try {
|
||||
List<PreKeyEntity> preKeys = socket.getPreKeys(pushAddress);
|
||||
|
||||
|
@@ -33,7 +33,6 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
Reference in New Issue
Block a user