2013-11-25 17:00:20 -08:00
|
|
|
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;
|
2014-02-02 19:38:06 -08:00
|
|
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
2014-03-19 11:14:15 -07:00
|
|
|
import org.thoughtcrime.securesms.service.PreKeyService;
|
2013-11-25 17:00:20 -08:00
|
|
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
|
|
|
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
2014-02-18 12:48:20 -08:00
|
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
2013-11-25 17:00:20 -08:00
|
|
|
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.MasterSecret;
|
|
|
|
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
|
|
|
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
|
|
|
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
|
|
|
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
|
|
|
import org.whispersystems.textsecure.crypto.ratchet.RatchetingSession;
|
|
|
|
import org.whispersystems.textsecure.push.PreKeyEntity;
|
|
|
|
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
|
|
|
import org.whispersystems.textsecure.storage.PreKeyRecord;
|
2014-02-02 19:38:06 -08:00
|
|
|
import org.whispersystems.textsecure.storage.RecipientDevice;
|
2013-11-25 17:00:20 -08:00
|
|
|
import org.whispersystems.textsecure.storage.Session;
|
|
|
|
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
|
|
|
import org.whispersystems.textsecure.util.Medium;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This class processes key exchange interactions.
|
|
|
|
*
|
|
|
|
* @author Moxie Marlinspike
|
|
|
|
*/
|
|
|
|
|
|
|
|
public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
|
|
|
|
|
|
|
|
private Context context;
|
2014-02-02 19:38:06 -08:00
|
|
|
private RecipientDevice recipientDevice;
|
2013-11-25 17:00:20 -08:00
|
|
|
private MasterSecret masterSecret;
|
|
|
|
private SessionRecordV2 sessionRecord;
|
|
|
|
|
2014-02-02 19:38:06 -08:00
|
|
|
public KeyExchangeProcessorV2(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice)
|
|
|
|
{
|
2013-11-25 17:00:20 -08:00
|
|
|
this.context = context;
|
2014-02-02 19:38:06 -08:00
|
|
|
this.recipientDevice = recipientDevice;
|
2013-11-25 17:00:20 -08:00
|
|
|
this.masterSecret = masterSecret;
|
2014-02-02 19:38:06 -08:00
|
|
|
this.sessionRecord = new SessionRecordV2(context, masterSecret, recipientDevice);
|
2013-11-25 17:00:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isTrusted(PreKeyWhisperMessage message) {
|
|
|
|
return isTrusted(message.getIdentityKey());
|
|
|
|
}
|
|
|
|
|
2014-02-16 15:23:49 -08:00
|
|
|
public boolean isTrusted(PreKeyEntity entity) {
|
|
|
|
return isTrusted(entity.getIdentityKey());
|
|
|
|
}
|
|
|
|
|
2013-11-25 17:00:20 -08:00
|
|
|
public boolean isTrusted(KeyExchangeMessage message) {
|
|
|
|
return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isTrusted(IdentityKey identityKey) {
|
2014-02-02 19:38:06 -08:00
|
|
|
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret,
|
|
|
|
recipientDevice.getRecipientId(),
|
2013-11-25 17:00:20 -08:00
|
|
|
identityKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isStale(KeyExchangeMessage m) {
|
|
|
|
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)m;
|
|
|
|
return
|
|
|
|
message.isResponse() &&
|
2014-03-17 17:24:00 -07:00
|
|
|
(!sessionRecord.getSessionState().hasPendingKeyExchange() ||
|
|
|
|
sessionRecord.getSessionState().getPendingKeyExchangeSequence() != message.getSequence()) &&
|
2013-11-25 17:00:20 -08:00
|
|
|
!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);
|
|
|
|
|
2014-02-02 19:38:06 -08:00
|
|
|
if (!PreKeyRecord.hasRecord(context, preKeyId) && SessionRecordV2.hasSession(context, masterSecret, recipientDevice)) {
|
2013-11-25 17:00:20 -08:00
|
|
|
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);
|
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
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();
|
2013-11-25 17:00:20 -08:00
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
if (!simultaneousInitiate) sessionRecord.clear();
|
|
|
|
else sessionRecord.archiveCurrentState();
|
|
|
|
|
|
|
|
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
|
|
|
ourBaseKey, theirBaseKey,
|
|
|
|
ourEphemeralKey, theirEphemeralKey,
|
|
|
|
ourIdentityKey, theirIdentityKey);
|
2013-11-25 17:00:20 -08:00
|
|
|
|
2014-02-02 19:38:06 -08:00
|
|
|
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
|
2014-03-17 17:24:00 -07:00
|
|
|
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
|
|
|
|
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
|
|
|
|
|
|
|
|
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
|
|
|
|
|
2013-11-25 17:00:20 -08:00
|
|
|
sessionRecord.save();
|
|
|
|
|
|
|
|
if (preKeyId != Medium.MAX_VALUE) {
|
|
|
|
PreKeyRecord.delete(context, preKeyId);
|
|
|
|
}
|
|
|
|
|
2014-03-19 11:14:15 -07:00
|
|
|
PreKeyService.initiateRefresh(context, masterSecret);
|
|
|
|
|
2013-11-25 17:00:20 -08:00
|
|
|
DatabaseFactory.getIdentityDatabase(context)
|
2014-02-02 19:38:06 -08:00
|
|
|
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), theirIdentityKey);
|
2013-11-25 17:00:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
public void processKeyExchangeMessage(PreKeyEntity message, long threadId)
|
|
|
|
throws InvalidKeyException
|
|
|
|
{
|
2014-03-19 15:25:50 -07:00
|
|
|
ECKeyPair ourBaseKey = Curve.generateKeyPairForSession(2, true);
|
|
|
|
ECKeyPair ourEphemeralKey = Curve.generateKeyPairForSession(2, true);
|
2013-11-27 17:50:38 -08:00
|
|
|
ECPublicKey theirBaseKey = message.getPublicKey();
|
2013-11-25 17:00:20 -08:00
|
|
|
ECPublicKey theirEphemeralKey = theirBaseKey;
|
|
|
|
IdentityKey theirIdentityKey = message.getIdentityKey();
|
|
|
|
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret,
|
|
|
|
ourBaseKey.getPublicKey()
|
|
|
|
.getType());
|
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
|
|
|
|
else sessionRecord.clear();
|
2013-11-25 17:00:20 -08:00
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
|
|
|
ourBaseKey, theirBaseKey, ourEphemeralKey,
|
2013-11-25 17:00:20 -08:00
|
|
|
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
|
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
sessionRecord.getSessionState().setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
|
|
|
|
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
|
|
|
|
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
|
|
|
|
|
2013-11-25 17:00:20 -08:00
|
|
|
sessionRecord.save();
|
|
|
|
|
|
|
|
DatabaseFactory.getIdentityDatabase(context)
|
2014-02-02 19:38:06 -08:00
|
|
|
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
|
2013-11-25 17:00:20 -08:00
|
|
|
|
2014-02-13 17:10:20 -08:00
|
|
|
if (threadId != -1) {
|
|
|
|
broadcastSecurityUpdateEvent(context, threadId);
|
|
|
|
}
|
2013-11-25 17:00:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void processKeyExchangeMessage(KeyExchangeMessage _message, long threadId)
|
|
|
|
throws InvalidMessageException
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)_message;
|
2014-02-02 19:38:06 -08:00
|
|
|
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
|
|
|
|
String.valueOf(recipientDevice.getRecipientId()),
|
|
|
|
false)
|
|
|
|
.getPrimaryRecipient();
|
2013-11-25 17:00:20 -08:00
|
|
|
|
|
|
|
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.");
|
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
|
2013-11-25 17:00:20 -08:00
|
|
|
Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate...");
|
2014-03-19 15:25:50 -07:00
|
|
|
ourBaseKey = Curve.generateKeyPairForType(message.getBaseKey().getType(), true);
|
|
|
|
ourEphemeralKey = Curve.generateKeyPairForType(message.getBaseKey().getType(), true);
|
2013-11-25 17:00:20 -08:00
|
|
|
ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, message.getBaseKey().getType());
|
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
|
|
|
|
ourEphemeralKey, ourIdentityKey);
|
2013-11-25 17:00:20 -08:00
|
|
|
} else {
|
|
|
|
Log.w("KeyExchangeProcessorV2", "We alredy have a pending initiate, responding as simultaneous initiate...");
|
2014-03-17 17:24:00 -07:00
|
|
|
ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
|
|
|
|
ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
|
|
|
|
ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
|
2013-11-25 17:00:20 -08:00
|
|
|
flags |= KeyExchangeMessageV2.SIMULTAENOUS_INITIATE_FLAG;
|
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
|
|
|
|
ourEphemeralKey, ourIdentityKey);
|
2013-11-25 17:00:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) {
|
2013-11-25 17:00:20 -08:00
|
|
|
Log.w("KeyExchangeProcessorV2", "No matching sequence for response. " +
|
|
|
|
"Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
|
|
|
|
ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
|
|
|
|
IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
|
2013-11-25 17:00:20 -08:00
|
|
|
|
|
|
|
sessionRecord.clear();
|
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
|
|
|
|
ourBaseKey, message.getBaseKey(),
|
2013-11-25 17:00:20 -08:00
|
|
|
ourEphemeralKey, message.getEphemeralKey(),
|
|
|
|
ourIdentityKey, message.getIdentityKey());
|
|
|
|
|
2014-03-17 17:24:00 -07:00
|
|
|
sessionRecord.getSessionState().setSessionVersion(message.getVersion());
|
2014-02-02 19:38:06 -08:00
|
|
|
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
|
2013-11-25 17:00:20 -08:00
|
|
|
sessionRecord.save();
|
|
|
|
|
|
|
|
DatabaseFactory.getIdentityDatabase(context)
|
2014-02-02 19:38:06 -08:00
|
|
|
.saveIdentity(masterSecret, recipientDevice.getRecipientId(), message.getIdentityKey());
|
2013-11-25 17:00:20 -08:00
|
|
|
|
|
|
|
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
|
|
|
|
|
|
|
|
broadcastSecurityUpdateEvent(context, threadId);
|
|
|
|
} catch (InvalidKeyException e) {
|
|
|
|
throw new InvalidMessageException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|