Collapse KeyExchangeMessage and KeyExchangeProcessor interfaces.

This commit is contained in:
Moxie Marlinspike 2014-04-22 15:15:07 -07:00
parent 14b8f97de2
commit a1db221caf
10 changed files with 398 additions and 511 deletions

View File

@ -32,7 +32,6 @@ import android.widget.TextView;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -149,18 +148,11 @@ public class ReceiveKeyActivity extends Activity {
private boolean isTrusted(KeyExchangeMessage message, PreKeyWhisperMessage messageBundle, IdentityKey identityUpdateMessage) {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
KeyExchangeProcessor processor = new KeyExchangeProcessor(this, masterSecret, recipientDevice);
if (message != null) {
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(this, masterSecret,
recipientDevice, message);
return processor.isTrusted(message);
} else if (messageBundle != null) {
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipientDevice);
return processor.isTrusted(messageBundle);
} else if (identityUpdateMessage != null) {
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipientDevice);
return processor.isTrusted(identityUpdateMessage);
}
if (message != null) return processor.isTrusted(message);
else if (messageBundle != null) return processor.isTrusted(messageBundle);
else if (identityUpdateMessage != null) return processor.isTrusted(identityUpdateMessage);
return false;
}
@ -186,7 +178,7 @@ public class ReceiveKeyActivity extends Activity {
} else if (getIntent().getBooleanExtra("is_identity_update", false)) {
this.identityUpdateMessage = new IdentityKey(Base64.decodeWithoutPadding(messageBody), 0);
} else {
this.keyExchangeMessage = KeyExchangeMessage.createFor(messageBody);
this.keyExchangeMessage = new KeyExchangeMessage(messageBody);
}
} catch (IOException e) {
throw new AssertionError(e);
@ -227,9 +219,13 @@ public class ReceiveKeyActivity extends Activity {
protected Void doInBackground(Void... params) {
if (keyExchangeMessage != null) {
try {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(ReceiveKeyActivity.this, masterSecret, recipientDevice, keyExchangeMessage);
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(),
recipientDeviceId);
KeyExchangeProcessor processor = new KeyExchangeProcessor(ReceiveKeyActivity.this,
masterSecret, recipientDevice);
processor.processKeyExchangeMessage(keyExchangeMessage, threadId);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsProcessedKeyExchange(messageId);
} catch (InvalidMessageException e) {
@ -240,7 +236,7 @@ public class ReceiveKeyActivity extends Activity {
} else if (keyExchangeMessageBundle != null) {
try {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(ReceiveKeyActivity.this,
KeyExchangeProcessor processor = new KeyExchangeProcessor(ReceiveKeyActivity.this,
masterSecret, recipientDevice);
processor.processKeyExchangeMessage(keyExchangeMessageBundle);

View File

@ -41,7 +41,6 @@ import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
@ -417,22 +416,20 @@ public class DecryptingQueue {
database.updateMessageBody(masterSecret, messageId, plaintextBody);
MessageNotifier.updateNotification(context, masterSecret);
} catch (InvalidMessageException ime) {
} catch (InvalidMessageException | IOException ime) {
Log.w("DecryptionQueue", ime);
database.markAsDecryptFailed(messageId);
} catch (IOException e) {
Log.w("DecryptionQueue", e);
database.markAsDecryptFailed(messageId);
}
}
private void handleKeyExchangeProcessing(String plaintextBody) {
if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false).getPrimaryRecipient();
Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false)
.getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId);
KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintextBody);
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, message);
KeyExchangeMessage message = new KeyExchangeMessage(plaintextBody);
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
if (processor.isStale(message)) {
DatabaseFactory.getEncryptingSmsDatabase(context).markAsStaleKeyExchange(messageId);
@ -443,13 +440,7 @@ public class DecryptingQueue {
} catch (InvalidVersionException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsInvalidVersionKeyExchange(messageId);
} catch (InvalidKeyException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
} catch (InvalidMessageException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
} catch (RecipientFormattingException e) {
} catch (InvalidMessageException | RecipientFormattingException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId);
} catch (LegacyMessageException e) {

View File

@ -22,7 +22,7 @@ import android.content.Context;
import android.content.DialogInterface;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
@ -62,12 +62,12 @@ public class KeyExchangeInitiator {
private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipient recipient) {
int sequence = getRandomSequence();
int flags = KeyExchangeMessageV2.INITIATE_FLAG;
int flags = KeyExchangeMessage.INITIATE_FLAG;
ECKeyPair baseKey = Curve.generateKeyPair(true);
ECKeyPair ephemeralKey = Curve.generateKeyPair(true);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
KeyExchangeMessageV2 message = new KeyExchangeMessageV2(sequence, flags,
KeyExchangeMessage message = new KeyExchangeMessage(sequence, flags,
baseKey.getPublicKey(),
ephemeralKey.getPublicKey(),
identityKey.getPublicKey());

View File

@ -1,45 +1,254 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.PreKeyService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Medium;
public abstract class KeyExchangeProcessor {
/**
* This class processes key exchange interactions.
*
* @author Moxie Marlinspike
*/
public class KeyExchangeProcessor {
public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE";
public abstract boolean isStale(KeyExchangeMessage message);
public abstract boolean isTrusted(KeyExchangeMessage message);
public abstract void processKeyExchangeMessage(KeyExchangeMessage message, long threadid)
throws InvalidMessageException;
private Context context;
private RecipientDevice recipientDevice;
private MasterSecret masterSecret;
private SessionStore sessionStore;
public static KeyExchangeProcessor createFor(Context context, MasterSecret masterSecret,
RecipientDevice recipientDevice,
KeyExchangeMessage message)
public KeyExchangeProcessor(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice)
{
return new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
this.context = context;
this.recipientDevice = recipientDevice;
this.masterSecret = masterSecret;
this.sessionStore = new TextSecureSessionStore(context, masterSecret);
}
public boolean isTrusted(PreKeyWhisperMessage message) {
return isTrusted(message.getIdentityKey());
}
public boolean isTrusted(PreKeyEntity entity) {
return isTrusted(entity.getIdentityKey());
}
public boolean isTrusted(KeyExchangeMessage message) {
return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
}
public boolean isTrusted(IdentityKey identityKey) {
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret,
recipientDevice.getRecipientId(),
identityKey);
}
public boolean isStale(KeyExchangeMessage message) {
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
return
message.isResponse() &&
(!sessionRecord.getSessionState().hasPendingKeyExchange() ||
sessionRecord.getSessionState().getPendingKeyExchangeSequence() != message.getSequence()) &&
!message.isResponseForSimultaneousInitiate();
}
public void processKeyExchangeMessage(PreKeyWhisperMessage message)
throws InvalidKeyIdException, InvalidKeyException
{
int preKeyId = message.getPreKeyId();
ECPublicKey theirBaseKey = message.getBaseKey();
ECPublicKey theirEphemeralKey = message.getWhisperMessage().getSenderEphemeral();
IdentityKey theirIdentityKey = message.getIdentityKey();
Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
if (!PreKeyRecord.hasRecord(context, preKeyId) &&
sessionStore.contains(recipientDevice.getRecipientId(), recipientDevice.getDeviceId()))
{
Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through...");
return;
}
if (!PreKeyRecord.hasRecord(context, preKeyId))
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
ECKeyPair ourEphemeralKey = ourBaseKey;
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
if (!simultaneousInitiate) sessionRecord.reset();
else sessionRecord.archiveCurrentState();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, theirBaseKey,
ourEphemeralKey, theirEphemeralKey,
ourIdentityKey, theirIdentityKey);
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
if (preKeyId != Medium.MAX_VALUE) {
PreKeyRecord.delete(context, preKeyId);
}
PreKeyService.initiateRefresh(context, masterSecret);
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), theirIdentityKey);
}
public void processKeyExchangeMessage(PreKeyEntity message, long threadId)
throws InvalidKeyException
{
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
ECKeyPair ourBaseKey = Curve.generateKeyPair(true);
ECKeyPair ourEphemeralKey = Curve.generateKeyPair(true);
ECPublicKey theirBaseKey = message.getPublicKey();
ECPublicKey theirEphemeralKey = theirBaseKey;
IdentityKey theirIdentityKey = message.getIdentityKey();
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
else sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, theirBaseKey, ourEphemeralKey,
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
sessionRecord.getSessionState().setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
if (threadId != -1) {
broadcastSecurityUpdateEvent(context, threadId);
}
}
public void processKeyExchangeMessage(KeyExchangeMessage message, long threadId)
throws InvalidMessageException
{
try {
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
String.valueOf(recipientDevice.getRecipientId()),
false)
.getPrimaryRecipient();
Log.w("KeyExchangeProcessor", "Received key exchange with sequence: " + message.getSequence());
if (message.isInitiate()) {
ECKeyPair ourBaseKey, ourEphemeralKey;
IdentityKeyPair ourIdentityKey;
int flags = KeyExchangeMessage.RESPONSE_FLAG;
Log.w("KeyExchangeProcessor", "KeyExchange is an initiate.");
if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
Log.w("KeyExchangeProcessor", "We don't have a pending initiate...");
ourBaseKey = Curve.generateKeyPair(true);
ourEphemeralKey = Curve.generateKeyPair(true);
ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourEphemeralKey, ourIdentityKey);
} else {
Log.w("KeyExchangeProcessor", "We alredy have a pending initiate, responding as simultaneous initiate...");
ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
flags |= KeyExchangeMessage.SIMULTAENOUS_INITIATE_FLAG;
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourEphemeralKey, ourIdentityKey);
}
KeyExchangeMessage ourMessage = new KeyExchangeMessage(message.getSequence(),
flags, ourBaseKey.getPublicKey(),
ourEphemeralKey.getPublicKey(),
ourIdentityKey.getPublicKey());
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient,
ourMessage.serialize());
MessageSender.send(context, masterSecret, textMessage, threadId, true);
}
if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) {
Log.w("KeyExchangeProcessor", "No matching sequence for response. " +
"Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
return;
}
ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, message.getBaseKey(),
ourEphemeralKey, message.getEphemeralKey(),
ourIdentityKey, message.getIdentityKey());
sessionRecord.getSessionState().setSessionVersion(message.getVersion());
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
broadcastSecurityUpdateEvent(context, threadId);
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
public static void broadcastSecurityUpdateEvent(Context context, long threadId) {

View File

@ -1,254 +0,0 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.service.PreKeyService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Medium;
/**
* This class processes key exchange interactions.
*
* @author Moxie Marlinspike
*/
public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
private Context context;
private RecipientDevice recipientDevice;
private MasterSecret masterSecret;
private SessionStore sessionStore;
public KeyExchangeProcessorV2(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice)
{
this.context = context;
this.recipientDevice = recipientDevice;
this.masterSecret = masterSecret;
this.sessionStore = new TextSecureSessionStore(context, masterSecret);
}
public boolean isTrusted(PreKeyWhisperMessage message) {
return isTrusted(message.getIdentityKey());
}
public boolean isTrusted(PreKeyEntity entity) {
return isTrusted(entity.getIdentityKey());
}
public boolean isTrusted(KeyExchangeMessage message) {
return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
}
public boolean isTrusted(IdentityKey identityKey) {
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret,
recipientDevice.getRecipientId(),
identityKey);
}
public boolean isStale(KeyExchangeMessage m) {
KeyExchangeMessageV2 message = (KeyExchangeMessageV2) m;
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
return
message.isResponse() &&
(!sessionRecord.getSessionState().hasPendingKeyExchange() ||
sessionRecord.getSessionState().getPendingKeyExchangeSequence() != message.getSequence()) &&
!message.isResponseForSimultaneousInitiate();
}
public void processKeyExchangeMessage(PreKeyWhisperMessage message)
throws InvalidKeyIdException, InvalidKeyException
{
int preKeyId = message.getPreKeyId();
ECPublicKey theirBaseKey = message.getBaseKey();
ECPublicKey theirEphemeralKey = message.getWhisperMessage().getSenderEphemeral();
IdentityKey theirIdentityKey = message.getIdentityKey();
Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
if (!PreKeyRecord.hasRecord(context, preKeyId) &&
sessionStore.contains(recipientDevice.getRecipientId(), recipientDevice.getDeviceId()))
{
Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through...");
return;
}
if (!PreKeyRecord.hasRecord(context, preKeyId))
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
ECKeyPair ourEphemeralKey = ourBaseKey;
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
if (!simultaneousInitiate) sessionRecord.reset();
else sessionRecord.archiveCurrentState();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, theirBaseKey,
ourEphemeralKey, theirEphemeralKey,
ourIdentityKey, theirIdentityKey);
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
if (preKeyId != Medium.MAX_VALUE) {
PreKeyRecord.delete(context, preKeyId);
}
PreKeyService.initiateRefresh(context, masterSecret);
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), theirIdentityKey);
}
public void processKeyExchangeMessage(PreKeyEntity message, long threadId)
throws InvalidKeyException
{
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
ECKeyPair ourBaseKey = Curve.generateKeyPair(true);
ECKeyPair ourEphemeralKey = Curve.generateKeyPair(true);
ECPublicKey theirBaseKey = message.getPublicKey();
ECPublicKey theirEphemeralKey = theirBaseKey;
IdentityKey theirIdentityKey = message.getIdentityKey();
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
else sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, theirBaseKey, ourEphemeralKey,
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
sessionRecord.getSessionState().setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
if (threadId != -1) {
broadcastSecurityUpdateEvent(context, threadId);
}
}
@Override
public void processKeyExchangeMessage(KeyExchangeMessage _message, long threadId)
throws InvalidMessageException
{
try {
KeyExchangeMessageV2 message = (KeyExchangeMessageV2) _message;
SessionRecord sessionRecord = sessionStore.get(recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
String.valueOf(recipientDevice.getRecipientId()),
false)
.getPrimaryRecipient();
Log.w("KeyExchangeProcessorV2", "Received key exchange with sequence: " + message.getSequence());
if (message.isInitiate()) {
ECKeyPair ourBaseKey, ourEphemeralKey;
IdentityKeyPair ourIdentityKey;
int flags = KeyExchangeMessageV2.RESPONSE_FLAG;
Log.w("KeyExchangeProcessorV2", "KeyExchange is an initiate.");
if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate...");
ourBaseKey = Curve.generateKeyPair(true);
ourEphemeralKey = Curve.generateKeyPair(true);
ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourEphemeralKey, ourIdentityKey);
} else {
Log.w("KeyExchangeProcessorV2", "We alredy have a pending initiate, responding as simultaneous initiate...");
ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
flags |= KeyExchangeMessageV2.SIMULTAENOUS_INITIATE_FLAG;
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourEphemeralKey, ourIdentityKey);
}
KeyExchangeMessageV2 ourMessage = new KeyExchangeMessageV2(message.getSequence(),
flags, ourBaseKey.getPublicKey(),
ourEphemeralKey.getPublicKey(),
ourIdentityKey.getPublicKey());
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient,
ourMessage.serialize());
MessageSender.send(context, masterSecret, textMessage, threadId, false);
}
if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) {
Log.w("KeyExchangeProcessorV2", "No matching sequence for response. " +
"Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
return;
}
ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, message.getBaseKey(),
ourEphemeralKey, message.getEphemeralKey(),
ourIdentityKey, message.getIdentityKey());
sessionRecord.getSessionState().setSessionVersion(message.getVersion());
sessionStore.put(recipientDevice.getRecipientId(), recipientDevice.getDeviceId(), sessionRecord);
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
broadcastSecurityUpdateEvent(context, threadId);
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
}

View File

@ -1,38 +1,144 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.crypto.protocol;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.WhisperProtos;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Util;
public abstract class KeyExchangeMessage {
public abstract IdentityKey getIdentityKey();
public abstract boolean hasIdentityKey();
public abstract int getMaxVersion();
public abstract int getVersion();
import java.io.IOException;
public static KeyExchangeMessage createFor(String rawMessage)
throws InvalidMessageException, InvalidKeyException, InvalidVersionException, LegacyMessageException
public class KeyExchangeMessage {
public static final int INITIATE_FLAG = 0x01;
public static final int RESPONSE_FLAG = 0X02;
public static final int SIMULTAENOUS_INITIATE_FLAG = 0x04;
private final int version;
private final int supportedVersion;
private final int sequence;
private final int flags;
private final ECPublicKey baseKey;
private final ECPublicKey ephemeralKey;
private final IdentityKey identityKey;
private final byte[] serialized;
public KeyExchangeMessage(int sequence, int flags,
ECPublicKey baseKey, ECPublicKey ephemeralKey,
IdentityKey identityKey)
{
return new KeyExchangeMessageV2(rawMessage);
this.supportedVersion = CiphertextMessage.CURRENT_VERSION;
this.version = CiphertextMessage.CURRENT_VERSION;
this.sequence = sequence;
this.flags = flags;
this.baseKey = baseKey;
this.ephemeralKey = ephemeralKey;
this.identityKey = identityKey;
byte[] version = {Conversions.intsToByteHighAndLow(this.version, this.supportedVersion)};
byte[] message = WhisperProtos.KeyExchangeMessage.newBuilder()
.setId((sequence << 5) | flags)
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
.setEphemeralKey(ByteString.copyFrom(ephemeralKey.serialize()))
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
.build().toByteArray();
this.serialized = Util.combine(version, message);
}
public KeyExchangeMessage(String serializedAndEncoded)
throws InvalidMessageException, InvalidVersionException, LegacyMessageException
{
try {
byte[] serialized = Base64.decodeWithoutPadding(serializedAndEncoded);
byte[][] parts = Util.split(serialized, 1, serialized.length - 1);
this.version = Conversions.highBitsToInt(parts[0][0]);
this.supportedVersion = Conversions.lowBitsToInt(parts[0][0]);
if (this.version <= CiphertextMessage.UNSUPPORTED_VERSION) {
throw new LegacyMessageException("Unsupported legacy version: " + this.version);
}
if (this.version > CiphertextMessage.CURRENT_VERSION) {
throw new InvalidVersionException("Unknown version: " + this.version);
}
WhisperProtos.KeyExchangeMessage message = WhisperProtos.KeyExchangeMessage.parseFrom(parts[1]);
if (!message.hasId() || !message.hasBaseKey() ||
!message.hasEphemeralKey() || !message.hasIdentityKey())
{
throw new InvalidMessageException("Some required fields missing!");
}
this.sequence = message.getId() >> 5;
this.flags = message.getId() & 0x1f;
this.serialized = serialized;
this.baseKey = Curve.decodePoint(message.getBaseKey().toByteArray(), 0);
this.ephemeralKey = Curve.decodePoint(message.getEphemeralKey().toByteArray(), 0);
this.identityKey = new IdentityKey(message.getIdentityKey().toByteArray(), 0);
} catch (InvalidKeyException | IOException e) {
throw new InvalidMessageException(e);
}
}
public int getVersion() {
return version;
}
public ECPublicKey getBaseKey() {
return baseKey;
}
public ECPublicKey getEphemeralKey() {
return ephemeralKey;
}
public IdentityKey getIdentityKey() {
return identityKey;
}
public boolean hasIdentityKey() {
return true;
}
public int getMaxVersion() {
return supportedVersion;
}
public boolean isResponse() {
return ((flags & RESPONSE_FLAG) != 0);
}
public boolean isInitiate() {
return (flags & INITIATE_FLAG) != 0;
}
public boolean isResponseForSimultaneousInitiate() {
return (flags & SIMULTAENOUS_INITIATE_FLAG) != 0;
}
public int getFlags() {
return flags;
}
public int getSequence() {
return sequence;
}
public String serialize() {
return Base64.encodeBytesWithoutPadding(serialized);
}
}

View File

@ -1,152 +0,0 @@
package org.thoughtcrime.securesms.crypto.protocol;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.WhisperProtos;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
public class KeyExchangeMessageV2 extends KeyExchangeMessage {
public static final int INITIATE_FLAG = 0x01;
public static final int RESPONSE_FLAG = 0X02;
public static final int SIMULTAENOUS_INITIATE_FLAG = 0x04;
private final int version;
private final int supportedVersion;
private final int sequence;
private final int flags;
private final ECPublicKey baseKey;
private final ECPublicKey ephemeralKey;
private final IdentityKey identityKey;
private final byte[] serialized;
public KeyExchangeMessageV2(int sequence, int flags,
ECPublicKey baseKey, ECPublicKey ephemeralKey,
IdentityKey identityKey)
{
this.supportedVersion = CiphertextMessage.CURRENT_VERSION;
this.version = CiphertextMessage.CURRENT_VERSION;
this.sequence = sequence;
this.flags = flags;
this.baseKey = baseKey;
this.ephemeralKey = ephemeralKey;
this.identityKey = identityKey;
byte[] version = {Conversions.intsToByteHighAndLow(this.version, this.supportedVersion)};
byte[] message = WhisperProtos.KeyExchangeMessage.newBuilder()
.setId((sequence << 5) | flags)
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
.setEphemeralKey(ByteString.copyFrom(ephemeralKey.serialize()))
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
.build().toByteArray();
this.serialized = Util.combine(version, message);
}
public KeyExchangeMessageV2(String serializedAndEncoded)
throws InvalidMessageException, InvalidVersionException, LegacyMessageException
{
try {
byte[] serialized = Base64.decodeWithoutPadding(serializedAndEncoded);
byte[][] parts = Util.split(serialized, 1, serialized.length - 1);
this.version = Conversions.highBitsToInt(parts[0][0]);
this.supportedVersion = Conversions.lowBitsToInt(parts[0][0]);
if (this.version <= CiphertextMessage.UNSUPPORTED_VERSION) {
throw new LegacyMessageException("Unsupported legacy version: " + this.version);
}
if (this.version > CiphertextMessage.CURRENT_VERSION) {
throw new InvalidVersionException("Unknown version: " + this.version);
}
WhisperProtos.KeyExchangeMessage message = WhisperProtos.KeyExchangeMessage.parseFrom(parts[1]);
if (!message.hasId() || !message.hasBaseKey() ||
!message.hasEphemeralKey() || !message.hasIdentityKey())
{
throw new InvalidMessageException("Some required fields missing!");
}
this.sequence = message.getId() >> 5;
this.flags = message.getId() & 0x1f;
this.serialized = serialized;
this.baseKey = Curve.decodePoint(message.getBaseKey().toByteArray(), 0);
this.ephemeralKey = Curve.decodePoint(message.getEphemeralKey().toByteArray(), 0);
this.identityKey = new IdentityKey(message.getIdentityKey().toByteArray(), 0);
} catch (InvalidProtocolBufferException e) {
throw new InvalidMessageException(e);
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
} catch (IOException e) {
throw new InvalidMessageException(e);
}
}
@Override
public int getVersion() {
return version;
}
public ECPublicKey getBaseKey() {
return baseKey;
}
public ECPublicKey getEphemeralKey() {
return ephemeralKey;
}
@Override
public IdentityKey getIdentityKey() {
return identityKey;
}
@Override
public boolean hasIdentityKey() {
return true;
}
@Override
public int getMaxVersion() {
return supportedVersion;
}
public boolean isResponse() {
return ((flags & RESPONSE_FLAG) != 0);
}
public boolean isInitiate() {
return (flags & INITIATE_FLAG) != 0;
}
public boolean isResponseForSimultaneousInitiate() {
return (flags & SIMULTAENOUS_INITIATE_FLAG) != 0;
}
public int getFlags() {
return flags;
}
public int getSequence() {
return sequence;
}
public String serialize() {
return Base64.encodeBytesWithoutPadding(serialized);
}
}

View File

@ -9,7 +9,6 @@ import com.google.protobuf.InvalidProtocolBufferException;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
@ -115,7 +114,7 @@ public class PushReceiver {
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSourceDevice());
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody());
if (processor.isTrusted(preKeyExchange)) {

View File

@ -23,7 +23,6 @@ import android.util.Pair;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -114,7 +113,7 @@ public class SmsReceiver {
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
SmsTransportDetails transportDetails = new SmsTransportDetails();
byte[] rawMessage = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(rawMessage);
@ -127,7 +126,7 @@ public class SmsReceiver {
IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, bundledMessageBody);
Pair<Long, Long> messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage);
Intent intent = new Intent(KeyExchangeProcessorV2.SECURITY_UPDATE_EVENT);
Intent intent = new Intent(KeyExchangeProcessor.SECURITY_UPDATE_EVENT);
intent.putExtra("thread_id", messageAndThreadId.second);
intent.setPackage(context.getPackageName());
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
@ -164,8 +163,8 @@ public class SmsReceiver {
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
KeyExchangeMessage exchangeMessage = KeyExchangeMessage.createFor(message.getMessageBody());
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, exchangeMessage);
KeyExchangeMessage exchangeMessage = new KeyExchangeMessage(message.getMessageBody());
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
if (processor.isStale(exchangeMessage)) {
message.setStale(true);
@ -180,13 +179,7 @@ public class SmsReceiver {
} catch (InvalidVersionException e) {
Log.w("SmsReceiver", e);
message.setInvalidVersion(true);
} catch (InvalidKeyException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
} catch (InvalidMessageException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
} catch (RecipientFormattingException e) {
} catch (InvalidMessageException | RecipientFormattingException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
} catch (LegacyMessageException e) {

View File

@ -23,7 +23,6 @@ import android.util.Log;
import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
@ -212,7 +211,7 @@ public class PushTransport extends BaseTransport {
for (int missingDeviceId : mismatchedDevices.getMissingDevices()) {
PushAddress address = PushAddress.create(context, recipientId, e164number, missingDeviceId);
PreKeyEntity preKey = socket.getPreKey(address);
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, address);
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, address);
if (processor.isTrusted(preKey)) {
processor.processKeyExchangeMessage(preKey, threadId);
@ -331,7 +330,7 @@ public class PushTransport extends BaseTransport {
for (PreKeyEntity preKey : preKeys) {
PushAddress device = PushAddress.create(context, pushAddress.getRecipientId(), pushAddress.getNumber(), preKey.getDeviceId());
KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, device);
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, device);
if (processor.isTrusted(preKey)) {
processor.processKeyExchangeMessage(preKey, threadId);