mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-20 17:48:39 +00:00
Introduce registration-time ID for detecting stale sessions.
1) At registration time, a client generates a random ID and transmits to the the server. 2) The server provides that registration ID to any client that requests a prekey. 3) Clients include that registration ID in any PreKeyWhisperMessage. 4) Clients include that registration ID in their sendMessage API call to the server. 5) The server verifies that the registration ID included in an API call is the same as the current registration ID for the destination device. Otherwise, it notifies the sender that their session is stale.
This commit is contained in:
@@ -33,6 +33,7 @@ import com.actionbarsherlock.app.SherlockActivity;
|
||||
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
|
||||
import org.thoughtcrime.securesms.service.RegistrationService;
|
||||
import org.thoughtcrime.securesms.util.ActionBarUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.RateLimitException;
|
||||
@@ -501,7 +502,8 @@ public class RegistrationProgressActivity extends SherlockActivity {
|
||||
protected Integer doInBackground(Void... params) {
|
||||
try {
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context, e164number, password);
|
||||
socket.verifyAccount(code, signalingKey, true);
|
||||
int registrationId = TextSecurePreferences.getLocalRegistrationId(context);
|
||||
socket.verifyAccount(code, signalingKey, true, registrationId);
|
||||
return SUCCESS;
|
||||
} catch (RateLimitException e) {
|
||||
Log.w("RegistrationProgressActivity", e);
|
||||
|
@@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
@@ -106,6 +107,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey,
|
||||
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
|
||||
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
|
||||
sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
|
||||
sessionRecord.setRemoteRegistrationId(message.getRegistrationId());
|
||||
sessionRecord.save();
|
||||
|
||||
if (preKeyId != Medium.MAX_VALUE) {
|
||||
@@ -134,6 +137,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
||||
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
|
||||
|
||||
sessionRecord.setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
|
||||
sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
|
||||
sessionRecord.setRemoteRegistrationId(message.getRegistrationId());
|
||||
sessionRecord.save();
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
|
@@ -227,6 +227,12 @@ public class RegistrationService extends Service {
|
||||
|
||||
String number = intent.getStringExtra("e164number");
|
||||
MasterSecret masterSecret = intent.getParcelableExtra("master_secret");
|
||||
int registrationId = TextSecurePreferences.getLocalRegistrationId(this);
|
||||
|
||||
if (registrationId == 0) {
|
||||
registrationId = Util.generateRegistrationId();
|
||||
TextSecurePreferences.setLocalRegistrationId(this, registrationId);
|
||||
}
|
||||
|
||||
try {
|
||||
String password = Util.getSecret(18);
|
||||
@@ -236,13 +242,14 @@ public class RegistrationService extends Service {
|
||||
initializeGcmRegistrationListener();
|
||||
initializePreKeyGenerator(masterSecret);
|
||||
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_CONNECTING, number));
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(this, number, password);
|
||||
socket.createAccount(false);
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_VERIFYING, number));
|
||||
String challenge = waitForChallenge();
|
||||
socket.verifyAccount(challenge, signalingKey, true);
|
||||
socket.verifyAccount(challenge, signalingKey, true, registrationId);
|
||||
|
||||
handleCommonRegistration(masterSecret, socket, number);
|
||||
markAsVerified(number, password, signalingKey);
|
||||
|
@@ -49,6 +49,8 @@ import org.whispersystems.textsecure.push.PushAttachmentPointer;
|
||||
import org.whispersystems.textsecure.push.PushBody;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.StaleDevices;
|
||||
import org.whispersystems.textsecure.push.StaleDevicesException;
|
||||
import org.whispersystems.textsecure.push.UnregisteredUserException;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
@@ -146,6 +148,9 @@ public class PushTransport extends BaseTransport {
|
||||
} catch (MismatchedDevicesException mde) {
|
||||
Log.w("PushTransport", mde);
|
||||
handleMismatchedDevices(socket, threadId, recipient, mde.getMismatchedDevices());
|
||||
} catch (StaleDevicesException ste) {
|
||||
Log.w("PushTransport", ste);
|
||||
handleStaleDevices(recipient, ste.getStaleDevices());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,6 +216,22 @@ public class PushTransport extends BaseTransport {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStaleDevices(Recipient recipient, StaleDevices staleDevices)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
long recipientId = recipient.getRecipientId();
|
||||
String e164number = Util.canonicalizeNumber(context, recipient.getNumber());
|
||||
|
||||
for (int staleDeviceId : staleDevices.getStaleDevices()) {
|
||||
PushAddress address = PushAddress.create(context, recipientId, e164number, staleDeviceId);
|
||||
SessionRecordV2.delete(context, address);
|
||||
}
|
||||
} catch (InvalidNumberException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPlaintextMessage(PushServiceSocket socket, SendReq message) throws IOException {
|
||||
String messageBody = PartParser.getMessageText(message.getBody());
|
||||
List<PushAttachmentPointer> attachments = getPushAttachmentPointers(socket, message.getBody());
|
||||
@@ -321,11 +342,12 @@ public class PushTransport extends BaseTransport {
|
||||
|
||||
SessionCipher cipher = SessionCipher.createFor(context, masterSecret, pushAddress);
|
||||
CiphertextMessage message = cipher.encrypt(plaintext);
|
||||
int remoteRegistrationId = cipher.getRemoteRegistrationId();
|
||||
|
||||
if (message.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) {
|
||||
return new PushBody(IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE, message.serialize());
|
||||
return new PushBody(IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE, remoteRegistrationId, message.serialize());
|
||||
} else if (message.getType() == CiphertextMessage.CURRENT_WHISPER_TYPE) {
|
||||
return new PushBody(IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE, message.serialize());
|
||||
return new PushBody(IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE, remoteRegistrationId, message.serialize());
|
||||
} else {
|
||||
throw new AssertionError("Unknown ciphertext type: " + message.getType());
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
|
||||
import org.whispersystems.textsecure.directory.Directory;
|
||||
|
@@ -43,6 +43,16 @@ public class TextSecurePreferences {
|
||||
private static final String DIRECTORY_FRESH_TIME_PREF = "pref_directory_refresh_time";
|
||||
private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications";
|
||||
|
||||
private static final String LOCAL_REGISTRATION_ID_PREF = "pref_local_registration_id";
|
||||
|
||||
public static int getLocalRegistrationId(Context context) {
|
||||
return getIntegerPreference(context, LOCAL_REGISTRATION_ID_PREF, 0);
|
||||
}
|
||||
|
||||
public static void setLocalRegistrationId(Context context, int registrationId) {
|
||||
setIntegerPrefrence(context, LOCAL_REGISTRATION_ID_PREF, registrationId);
|
||||
}
|
||||
|
||||
public static boolean isInThreadNotifications(Context context) {
|
||||
return getBooleanPreference(context, IN_THREAD_NOTIFICATION_PREF, true);
|
||||
}
|
||||
|
Reference in New Issue
Block a user