diff --git a/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemoryIdentityKeyStore.java b/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemoryIdentityKeyStore.java index 520dd7d4b4..e510a26d1b 100644 --- a/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemoryIdentityKeyStore.java +++ b/libaxolotl/src/androidTest/java/org/whispersystems/test/InMemoryIdentityKeyStore.java @@ -44,4 +44,10 @@ public class InMemoryIdentityKeyStore implements IdentityKeyStore { public void saveIdentity(long recipientId, IdentityKey identityKey) { trustedKeys.put(recipientId, identityKey); } + + @Override + public boolean isTrustedIdentity(long recipientId, IdentityKey identityKey) { + IdentityKey trusted = trustedKeys.get(recipientId); + return (trusted == null || trusted.equals(identityKey)); + } } diff --git a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java index 713ef573b3..01980a0ae5 100644 --- a/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java +++ b/libaxolotl/src/androidTest/java/org/whispersystems/test/SessionBuilderTest.java @@ -11,6 +11,8 @@ import org.whispersystems.libaxolotl.InvalidVersionException; import org.whispersystems.libaxolotl.LegacyMessageException; import org.whispersystems.libaxolotl.SessionBuilder; import org.whispersystems.libaxolotl.SessionCipher; +import org.whispersystems.libaxolotl.StaleKeyExchangeException; +import org.whispersystems.libaxolotl.UntrustedIdentityException; import org.whispersystems.libaxolotl.ecc.Curve; import org.whispersystems.libaxolotl.ecc.ECKeyPair; import org.whispersystems.libaxolotl.ecc.ECPublicKey; @@ -33,8 +35,7 @@ public class SessionBuilderTest extends AndroidTestCase { private static final long BOB_RECIPIENT_ID = 2L; public void testBasicPreKey() - throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException - { + throws InvalidKeyException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException, UntrustedIdentityException { SessionStore aliceSessionStore = new InMemorySessionStore(); PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); @@ -74,9 +75,55 @@ public class SessionBuilderTest extends AndroidTestCase { byte[] plaintext = bobSessionCipher.decrypt(incomingMessage.getWhisperMessage().serialize()); assertTrue(originalMessage.equals(new String(plaintext))); + + CiphertextMessage bobOutgoingMessage = bobSessionCipher.encrypt(originalMessage.getBytes()); + assertTrue(bobOutgoingMessage.getType() == CiphertextMessage.WHISPER_TYPE); + + byte[] alicePlaintext = aliceSessionCipher.decrypt(bobOutgoingMessage.serialize()); + assertTrue(new String(alicePlaintext).equals(originalMessage)); + + runInteraction(aliceSessionStore, bobSessionStore); + + aliceSessionStore = new InMemorySessionStore(); + aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); + aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, + aliceIdentityKeyStore, + BOB_RECIPIENT_ID, 1); + aliceSessionCipher = new SessionCipher(aliceSessionStore, BOB_RECIPIENT_ID, 1); + + bobPreKey = new InMemoryPreKey(31338, Curve.generateKeyPair(true), + bobIdentityKeyStore.getIdentityKeyPair().getPublicKey(), + bobIdentityKeyStore.getLocalRegistrationId()); + + bobPreKeyStore.store(31338, bobPreKey); + aliceSessionBuilder.process(bobPreKey); + + outgoingMessage = aliceSessionCipher.encrypt(originalMessage.getBytes()); + + try { + bobSessionBuilder.process(new PreKeyWhisperMessage(outgoingMessage.serialize())); + throw new AssertionError("shouldn't be trusted!"); + } catch (UntrustedIdentityException uie) { + bobIdentityKeyStore.saveIdentity(ALICE_RECIPIENT_ID, new PreKeyWhisperMessage(outgoingMessage.serialize()).getIdentityKey()); + bobSessionBuilder.process(new PreKeyWhisperMessage(outgoingMessage.serialize())); + } + + plaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(outgoingMessage.serialize()).getWhisperMessage().serialize()); + assertTrue(new String(plaintext).equals(originalMessage)); + + bobPreKey = new InMemoryPreKey(31337, Curve.generateKeyPair(true), + aliceIdentityKeyStore.getIdentityKeyPair().getPublicKey(), + bobIdentityKeyStore.getLocalRegistrationId()); + + try { + aliceSessionBuilder.process(bobPreKey); + throw new AssertionError("shoulnd't be trusted!"); + } catch (UntrustedIdentityException uie) { + // good + } } - public void testBasicKeyExchange() throws InvalidKeyException, LegacyMessageException, InvalidMessageException, DuplicateMessageException { + public void testBasicKeyExchange() throws InvalidKeyException, LegacyMessageException, InvalidMessageException, DuplicateMessageException, UntrustedIdentityException, StaleKeyExchangeException { SessionStore aliceSessionStore = new InMemorySessionStore(); PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); @@ -104,11 +151,28 @@ public class SessionBuilderTest extends AndroidTestCase { assertTrue(bobSessionStore.contains(ALICE_RECIPIENT_ID, 1)); runInteraction(aliceSessionStore, bobSessionStore); + + aliceSessionStore = new InMemorySessionStore(); + aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); + aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore, + aliceIdentityKeyStore, BOB_RECIPIENT_ID, 1); + aliceKeyExchangeMessage = aliceSessionBuilder.process(); + + try { + bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage); + throw new AssertionError("This identity shouldn't be trusted!"); + } catch (UntrustedIdentityException uie) { + bobIdentityKeyStore.saveIdentity(ALICE_RECIPIENT_ID, aliceKeyExchangeMessage.getIdentityKey()); + bobKeyExchangeMessage = bobSessionBuilder.process(aliceKeyExchangeMessage); + } + + assertTrue(aliceSessionBuilder.process(bobKeyExchangeMessage) == null); + + runInteraction(aliceSessionStore, bobSessionStore); } public void testSimultaneousKeyExchange() - throws InvalidKeyException, DuplicateMessageException, LegacyMessageException, InvalidMessageException - { + throws InvalidKeyException, DuplicateMessageException, LegacyMessageException, InvalidMessageException, UntrustedIdentityException, StaleKeyExchangeException { SessionStore aliceSessionStore = new InMemorySessionStore(); PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore(); IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore(); diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java index a27f829fc4..11d7f06173 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionBuilder.java @@ -13,6 +13,7 @@ import org.whispersystems.libaxolotl.state.PreKey; import org.whispersystems.libaxolotl.state.PreKeyRecord; import org.whispersystems.libaxolotl.state.PreKeyStore; import org.whispersystems.libaxolotl.state.SessionRecord; +import org.whispersystems.libaxolotl.state.SessionState; import org.whispersystems.libaxolotl.state.SessionStore; import org.whispersystems.libaxolotl.util.KeyHelper; import org.whispersystems.libaxolotl.util.Medium; @@ -77,16 +78,19 @@ public class SessionBuilder { * that corresponds to the PreKey ID in * the message. * @throws org.whispersystems.libaxolotl.InvalidKeyException when the message is formatted incorrectly. + * @throws org.whispersystems.libaxolotl.UntrustedIdentityException when the {@link IdentityKey} of the sender is untrusted. */ public void process(PreKeyWhisperMessage message) - throws InvalidKeyIdException, InvalidKeyException + throws InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException { int preKeyId = message.getPreKeyId(); ECPublicKey theirBaseKey = message.getBaseKey(); ECPublicKey theirEphemeralKey = message.getWhisperMessage().getSenderEphemeral(); IdentityKey theirIdentityKey = message.getIdentityKey(); - Log.w(TAG, "Received pre-key with local key ID: " + preKeyId); + if (!identityKeyStore.isTrustedIdentity(recipientId, theirIdentityKey)) { + throw new UntrustedIdentityException(); + } if (!preKeyStore.contains(preKeyId) && sessionStore.contains(recipientId, deviceId)) @@ -134,8 +138,16 @@ public class SessionBuilder { * @param preKey A PreKey for the destination recipient, retrieved from a server. * @throws InvalidKeyException when the {@link org.whispersystems.libaxolotl.state.PreKey} is * badly formatted. + * @throws org.whispersystems.libaxolotl.UntrustedIdentityException when the sender's + * {@link IdentityKey} is not + * trusted. */ - public void process(PreKey preKey) throws InvalidKeyException { + public void process(PreKey preKey) throws InvalidKeyException, UntrustedIdentityException { + + if (!identityKeyStore.isTrustedIdentity(recipientId, preKey.getIdentityKey())) { + throw new UntrustedIdentityException(); + } + SessionRecord sessionRecord = sessionStore.load(recipientId, deviceId); ECKeyPair ourBaseKey = Curve.generateKeyPair(true); ECKeyPair ourEphemeralKey = Curve.generateKeyPair(true); @@ -168,43 +180,34 @@ public class SessionBuilder { * @return The KeyExchangeMessage to respond with, or null if no response is necessary. * @throws InvalidKeyException if the received KeyExchangeMessage is badly formatted. */ - public KeyExchangeMessage process(KeyExchangeMessage message) throws InvalidKeyException { + public KeyExchangeMessage process(KeyExchangeMessage message) + throws InvalidKeyException, UntrustedIdentityException, StaleKeyExchangeException + { + + if (!identityKeyStore.isTrustedIdentity(recipientId, message.getIdentityKey())) { + throw new UntrustedIdentityException(); + } + KeyExchangeMessage responseMessage = null; SessionRecord sessionRecord = sessionStore.load(recipientId, deviceId); Log.w(TAG, "Received key exchange with sequence: " + message.getSequence()); if (message.isInitiate()) { - ECKeyPair ourBaseKey, ourEphemeralKey; - IdentityKeyPair ourIdentityKey; - - int flags = KeyExchangeMessage.RESPONSE_FLAG; - Log.w(TAG, "KeyExchange is an initiate."); + responseMessage = processInitiate(sessionRecord, message); + } - if (!sessionRecord.getSessionState().hasPendingKeyExchange()) { - Log.w(TAG, "We don't have a pending initiate..."); - ourBaseKey = Curve.generateKeyPair(true); - ourEphemeralKey = Curve.generateKeyPair(true); - ourIdentityKey = identityKeyStore.getIdentityKeyPair(); + if (message.isResponse()) { + SessionState sessionState = sessionRecord.getSessionState(); + boolean hasPendingKeyExchange = sessionState.hasPendingKeyExchange(); + boolean isSimultaneousInitiateResponse = message.isResponseForSimultaneousInitiate(); - sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey, - ourEphemeralKey, ourIdentityKey); - } else { - Log.w(TAG, "We already 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); + if ((!hasPendingKeyExchange || sessionState.getPendingKeyExchangeSequence() != message.getSequence()) && + !isSimultaneousInitiateResponse) + { + throw new StaleKeyExchangeException(); } - - responseMessage = new KeyExchangeMessage(message.getSequence(), - flags, ourBaseKey.getPublicKey(), - ourEphemeralKey.getPublicKey(), - ourIdentityKey.getPublicKey()); } if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) { @@ -232,6 +235,39 @@ public class SessionBuilder { return responseMessage; } + private KeyExchangeMessage processInitiate(SessionRecord sessionRecord, KeyExchangeMessage message) + throws InvalidKeyException + { + ECKeyPair ourBaseKey, ourEphemeralKey; + IdentityKeyPair ourIdentityKey; + + int flags = KeyExchangeMessage.RESPONSE_FLAG; + + if (!sessionRecord.getSessionState().hasPendingKeyExchange()) { + Log.w(TAG, "We don't have a pending initiate..."); + ourBaseKey = Curve.generateKeyPair(true); + ourEphemeralKey = Curve.generateKeyPair(true); + ourIdentityKey = identityKeyStore.getIdentityKeyPair(); + + sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey, + ourEphemeralKey, ourIdentityKey); + } else { + Log.w(TAG, "We already 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); + } + + return new KeyExchangeMessage(message.getSequence(), + flags, ourBaseKey.getPublicKey(), + ourEphemeralKey.getPublicKey(), + ourIdentityKey.getPublicKey()); + } + /** * Initiate a new session by sending an initial KeyExchangeMessage to the recipient. * diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/StaleKeyExchangeException.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/StaleKeyExchangeException.java new file mode 100644 index 0000000000..f94a30087c --- /dev/null +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/StaleKeyExchangeException.java @@ -0,0 +1,4 @@ +package org.whispersystems.libaxolotl; + +public class StaleKeyExchangeException extends Throwable { +} diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/UntrustedIdentityException.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/UntrustedIdentityException.java new file mode 100644 index 0000000000..f434b92301 --- /dev/null +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/UntrustedIdentityException.java @@ -0,0 +1,4 @@ +package org.whispersystems.libaxolotl; + +public class UntrustedIdentityException extends Exception { +} diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java index 4b6d5e60a6..d2024f7836 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/IdentityKeyStore.java @@ -37,4 +37,21 @@ public interface IdentityKeyStore { */ public void saveIdentity(long recipientId, IdentityKey identityKey); + + /** + * Verify a remote client's identity key. + *
+ * Determine whether a remote client's identity is trusted. Convention is
+ * that the TextSecure protocol is 'trust on first use.' This means that
+ * an identity key is considered 'trusted' if there is no entry for the recipient
+ * in the local store, or if it matches the saved key for a recipient in the local
+ * store. Only if it mismatches an entry in the local store is it considered
+ * 'untrusted.'
+ *
+ * @param recipientId The recipient ID of the remote client.
+ * @param identityKey The identity key to verify.
+ * @return true if trusted, false if untrusted.
+ */
+ public boolean isTrustedIdentity(long recipientId, IdentityKey identityKey);
+
}
diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
index dc63a4f3bf..37ebcebf1b 100644
--- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
+++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
@@ -29,9 +29,11 @@ import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
+import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
+import org.thoughtcrime.securesms.crypto.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SendReceiveService;
@@ -43,9 +45,12 @@ import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
+import org.whispersystems.libaxolotl.StaleKeyExchangeException;
+import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
+import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.textsecure.crypto.IdentityKeyParcelable;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.push.IncomingPushMessage;
@@ -91,14 +96,8 @@ public class ReceiveKeyActivity extends Activity {
try {
initializeKey();
initializeText();
- } catch (InvalidKeyException ike) {
+ } catch (InvalidKeyException | InvalidVersionException | InvalidMessageException | LegacyMessageException ike) {
Log.w("ReceiveKeyActivity", ike);
- } catch (InvalidVersionException ive) {
- Log.w("ReceiveKeyActivity", ive);
- } catch (InvalidMessageException e) {
- Log.w("ReceiveKeyActivity", e);
- } catch (LegacyMessageException e) {
- Log.w("ReceiveKeyActivity", e);
}
initializeListeners();
}
@@ -147,12 +146,12 @@ 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);
+ long recipientId = recipient.getRecipientId();
+ IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(this, masterSecret);
- if (message != null) return processor.isTrusted(message);
- else if (messageBundle != null) return processor.isTrusted(messageBundle);
- else if (identityUpdateMessage != null) return processor.isTrusted(identityUpdateMessage);
+ if (message != null) return identityKeyStore.isTrustedIdentity(recipientId, message.getIdentityKey());
+ else if (messageBundle != null) return identityKeyStore.isTrustedIdentity(recipientId, messageBundle.getIdentityKey());
+ else if (identityUpdateMessage != null) return identityKeyStore.isTrustedIdentity(recipientId, identityUpdateMessage);
return false;
}
@@ -224,6 +223,10 @@ public class ReceiveKeyActivity extends Activity {
KeyExchangeProcessor processor = new KeyExchangeProcessor(ReceiveKeyActivity.this,
masterSecret, recipientDevice);
+ IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(ReceiveKeyActivity.this,
+ masterSecret);
+ identityKeyStore.saveIdentity(recipient.getRecipientId(), keyExchangeMessage.getIdentityKey());
+
processor.processKeyExchangeMessage(keyExchangeMessage, threadId);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
@@ -232,12 +235,21 @@ public class ReceiveKeyActivity extends Activity {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsCorruptKeyExchange(messageId);
+ } catch (StaleKeyExchangeException e) {
+ DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
+ .markAsStaleKeyExchange(messageId);
+ } catch (UntrustedIdentityException e) {
+ throw new AssertionError(e);
}
} else if (keyExchangeMessageBundle != null) {
try {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
KeyExchangeProcessor processor = new KeyExchangeProcessor(ReceiveKeyActivity.this,
- masterSecret, recipientDevice);
+ masterSecret, recipientDevice);
+ IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(ReceiveKeyActivity.this,
+ masterSecret);
+
+ identityKeyStore.saveIdentity(recipient.getRecipientId(), keyExchangeMessageBundle.getIdentityKey());
processor.processKeyExchangeMessage(keyExchangeMessageBundle);
CiphertextMessage bundledMessage = keyExchangeMessageBundle.getWhisperMessage();
@@ -264,18 +276,13 @@ public class ReceiveKeyActivity extends Activity {
threadId, recipient.getNumber(), recipientDeviceId,
messageBody, true, false, false);
}
- } catch (InvalidKeyIdException e) {
+ } catch (InvalidKeyIdException | InvalidNumberException | InvalidKeyException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsCorruptKeyExchange(messageId);
- } catch (InvalidKeyException e) {
+ } catch (UntrustedIdentityException e) {
Log.w("ReceiveKeyActivity", e);
- DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
- .markAsCorruptKeyExchange(messageId);
- } catch (InvalidNumberException e) {
- Log.w("ReceiveKeyActivity", e);
- DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
- .markAsCorruptKeyExchange(messageId);
+ Toast.makeText(ReceiveKeyActivity.this, "Untrusted!", Toast.LENGTH_LONG).show();
}
} else if (identityUpdateMessage != null) {
DatabaseFactory.getIdentityDatabase(ReceiveKeyActivity.this)
diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
index 50d2f149da..435b11ae74 100644
--- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
+++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
@@ -45,6 +45,8 @@ import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.SessionCipher;
+import org.whispersystems.libaxolotl.StaleKeyExchangeException;
+import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.SessionStore;
@@ -433,12 +435,8 @@ public class DecryptingQueue {
KeyExchangeMessage message = new KeyExchangeMessage(Base64.decodeWithoutPadding(plaintextBody));
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
- if (processor.isStale(message)) {
- DatabaseFactory.getEncryptingSmsDatabase(context).markAsStaleKeyExchange(messageId);
- } else if (processor.isTrusted(message)) {
- DatabaseFactory.getEncryptingSmsDatabase(context).markAsProcessedKeyExchange(messageId);
- processor.processKeyExchangeMessage(message, threadId);
- }
+ processor.processKeyExchangeMessage(message, threadId);
+ DatabaseFactory.getEncryptingSmsDatabase(context).markAsProcessedKeyExchange(messageId);
} catch (InvalidVersionException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsInvalidVersionKeyExchange(messageId);
@@ -448,6 +446,11 @@ public class DecryptingQueue {
} catch (LegacyMessageException e) {
Log.w("DecryptingQueue", e);
DatabaseFactory.getEncryptingSmsDatabase(context).markAsLegacyVersion(messageId);
+ } catch (StaleKeyExchangeException e) {
+ Log.w("DecryptingQueue", e);
+ DatabaseFactory.getEncryptingSmsDatabase(context).markAsStaleKeyExchange(messageId);
+ } catch (UntrustedIdentityException e) {
+ Log.w("DecryptingQueue", e);
}
}
}
diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
index c0577d5322..5ce12a56bc 100644
--- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
+++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
@@ -3,22 +3,20 @@ package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.content.Intent;
-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.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.SessionBuilder;
+import org.whispersystems.libaxolotl.StaleKeyExchangeException;
+import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyStore;
-import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.push.PreKeyEntity;
@@ -41,7 +39,6 @@ public class KeyExchangeProcessor {
private RecipientDevice recipientDevice;
private MasterSecret masterSecret;
private SessionBuilder sessionBuilder;
- private SessionStore sessionStore;
public KeyExchangeProcessor(Context context, MasterSecret masterSecret, RecipientDevice recipientDevice)
{
@@ -51,51 +48,22 @@ public class KeyExchangeProcessor {
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret);
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
+ SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
- this.sessionStore = new TextSecureSessionStore(context, masterSecret);
this.sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, identityKeyStore,
recipientDevice.getRecipientId(),
recipientDevice.getDeviceId());
}
- 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.load(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
+ throws InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException
{
sessionBuilder.process(message);
PreKeyService.initiateRefresh(context, masterSecret);
}
public void processKeyExchangeMessage(PreKeyEntity message, long threadId)
- throws InvalidKeyException
+ throws InvalidKeyException, UntrustedIdentityException
{
sessionBuilder.process(message);
@@ -104,24 +72,25 @@ public class KeyExchangeProcessor {
}
}
- public void processKeyExchangeMessage(KeyExchangeMessage message, long threadId)
- throws InvalidKeyException
+ public OutgoingKeyExchangeMessage processKeyExchangeMessage(KeyExchangeMessage message, long threadId)
+ throws InvalidKeyException, UntrustedIdentityException, StaleKeyExchangeException
{
KeyExchangeMessage responseMessage = sessionBuilder.process(message);
- Recipient recipient = RecipientFactory.getRecipientsForIds(context,
+ Recipient recipient = RecipientFactory.getRecipientsForIds(context,
String.valueOf(recipientDevice.getRecipientId()),
false)
.getPrimaryRecipient();
- if (responseMessage != null) {
- String serializedResponse = Base64.encodeBytesWithoutPadding(responseMessage.serialize());
- OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, serializedResponse);
- MessageSender.send(context, masterSecret, textMessage, threadId, true);
- }
-
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
broadcastSecurityUpdateEvent(context, threadId);
+
+ if (responseMessage != null) {
+ String serializedResponse = Base64.encodeBytesWithoutPadding(responseMessage.serialize());
+ return new OutgoingKeyExchangeMessage(recipient, serializedResponse);
+ } else {
+ return null;
+ }
}
public static void broadcastSecurityUpdateEvent(Context context, long threadId) {
diff --git a/src/org/thoughtcrime/securesms/crypto/TextSecureIdentityKeyStore.java b/src/org/thoughtcrime/securesms/crypto/TextSecureIdentityKeyStore.java
index e870c02a8c..f2a5f18855 100644
--- a/src/org/thoughtcrime/securesms/crypto/TextSecureIdentityKeyStore.java
+++ b/src/org/thoughtcrime/securesms/crypto/TextSecureIdentityKeyStore.java
@@ -33,4 +33,10 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
public void saveIdentity(long recipientId, IdentityKey identityKey) {
DatabaseFactory.getIdentityDatabase(context).saveIdentity(masterSecret, recipientId, identityKey);
}
+
+ @Override
+ public boolean isTrustedIdentity(long recipientId, IdentityKey identityKey) {
+ return DatabaseFactory.getIdentityDatabase(context)
+ .isValidIdentity(masterSecret, recipientId, identityKey);
+ }
}
diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java
index b202310f08..81f8d073df 100644
--- a/src/org/thoughtcrime/securesms/service/PushReceiver.java
+++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
+import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
@@ -117,15 +118,16 @@ public class PushReceiver {
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody());
- if (processor.isTrusted(preKeyExchange)) {
+ try {
processor.processKeyExchangeMessage(preKeyExchange);
IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getWhisperMessage().serialize());
handleReceivedSecureMessage(masterSecret, bundledMessage);
- } else {
- String encoded = Base64.encodeBytes(message.getBody());
- IncomingTextMessage textMessage = new IncomingTextMessage(message, encoded, null);
- IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
+ } catch (UntrustedIdentityException uie) {
+ Log.w("PushReceiver", uie);
+ String encoded = Base64.encodeBytes(message.getBody());
+ IncomingTextMessage textMessage = new IncomingTextMessage(message, encoded, null);
+ IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
Pair