session-android/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java
Moxie Marlinspike 19dddd7adf Support for an 'end session' protocol message.
1) On the push side, this message is a flag in PushMessageContent.
   Any secure message with that flag will terminate the current
   sessin.

2) On the SMS side, there is an "end session" wire type and
   the convention that a message with this wire type must be
   secure and contain the string "TERMINATE."
2014-02-19 13:50:32 -08:00

153 lines
6.6 KiB
Java

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.KeyExchangeMessageV1;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.KeyPair;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.CanonicalRecipient;
import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
import org.whispersystems.textsecure.storage.SessionRecordV1;
import org.whispersystems.textsecure.util.Conversions;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
* This class processes key exchange interactions.
*
* @author Moxie Marlinspike
*/
public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
private Context context;
private CanonicalRecipient recipient;
private MasterSecret masterSecret;
private LocalKeyRecord localKeyRecord;
private RemoteKeyRecord remoteKeyRecord;
private SessionRecordV1 sessionRecord;
public KeyExchangeProcessorV1(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) {
this.context = context;
this.recipient = recipient;
this.masterSecret = masterSecret;
this.remoteKeyRecord = new RemoteKeyRecord(context, recipient);
this.localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
this.sessionRecord = new SessionRecordV1(context, masterSecret, recipient);
}
@Override
public boolean isTrusted(KeyExchangeMessage message) {
return message.hasIdentityKey() && isTrusted(message.getIdentityKey());
}
public boolean isTrusted(IdentityKey identityKey) {
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret,
recipient.getRecipientId(),
identityKey);
}
public boolean hasInitiatedSession() {
return localKeyRecord.getCurrentKeyPair() != null;
}
private boolean needsResponseFromUs() {
return !hasInitiatedSession() || remoteKeyRecord.getCurrentRemoteKey() != null;
}
@Override
public boolean isStale(KeyExchangeMessage _message) {
KeyExchangeMessageV1 message = (KeyExchangeMessageV1)_message;
int responseKeyId = Conversions.highBitsToMedium(message.getRemoteKey().getId());
Log.w("KeyExchangeProcessor", "Key Exchange High ID Bits: " + responseKeyId);
return responseKeyId != 0 &&
(localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId);
}
@Override
public void processKeyExchangeMessage(KeyExchangeMessage _message, long threadId) {
KeyExchangeMessageV1 message = (KeyExchangeMessageV1) _message;
int initiateKeyId = Conversions.lowBitsToMedium(message.getRemoteKey().getId());
Recipient recipient = RecipientFactory.getRecipientsForIds(context,
this.recipient.getRecipientId()+"",
true).getPrimaryRecipient();
message.getRemoteKey().setId(initiateKeyId);
if (needsResponseFromUs()) {
localKeyRecord = initializeRecordFor(context, masterSecret, recipient);
KeyExchangeMessageV1 ourMessage = new KeyExchangeMessageV1(context, masterSecret,
Math.min(CiphertextMessage.LEGACY_VERSION,
message.getMaxVersion()),
localKeyRecord, initiateKeyId);
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize());
Log.w("KeyExchangeProcessorV1", "Responding with key exchange message fingerprint: " + ourMessage.getRemoteKey().getFingerprint());
Log.w("KeyExchangeProcessorV1", "Which has a local key record fingerprint: " + localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprint());
MessageSender.send(context, masterSecret, textMessage, threadId);
}
remoteKeyRecord.setCurrentRemoteKey(message.getRemoteKey());
remoteKeyRecord.setLastRemoteKey(message.getRemoteKey());
remoteKeyRecord.save();
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(message.getIdentityKey());
sessionRecord.setSessionVersion(Math.min(1, message.getMaxVersion()));
Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(1, message.getMaxVersion()));
sessionRecord.save();
if (message.hasIdentityKey()) {
DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipient.getRecipientId(), message.getIdentityKey());
}
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
broadcastSecurityUpdateEvent(context, threadId);
}
public LocalKeyRecord initializeRecordFor(Context context,
MasterSecret masterSecret,
CanonicalRecipient recipient)
{
Log.w("KeyExchangeProcessorV1", "Initializing local key pairs...");
try {
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
int initialId = secureRandom.nextInt(4094) + 1;
KeyPair currentPair = new KeyPair(initialId, Curve.generateKeyPairForSession(1), masterSecret);
KeyPair nextPair = new KeyPair(initialId + 1, Curve.generateKeyPairForSession(1), masterSecret);
LocalKeyRecord record = new LocalKeyRecord(context, masterSecret, recipient);
record.setCurrentKeyPair(currentPair);
record.setNextKeyPair(nextPair);
record.save();
return record;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
}