Skip to content

Commit

Permalink
Rearrange decrypt API.
Browse files Browse the repository at this point in the history
1) Change SessionBuilder to only establish sessions via
   KeyExchangeMessage and PreKeyBundles.

2) Change SessionCipher to decrypt either WhisperMessage
   or PreKeyWhisperMessage items, automatically building
   a session for the latter.

3) Change SessionCipher to tear down new sessions built
   with PreKeyWhisperMessages if the embedded WhsiperMessage
   fails to decrypt.
  • Loading branch information
moxie0 committed Oct 20, 2014
1 parent 42cf53e commit 819982a
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 180 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.whispersystems.libaxolotl;

public class NoSessionException extends Exception {
public NoSessionException(String s) {
super(s);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,31 +89,35 @@ public SessionBuilder(SessionStore sessionStore,
* @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)
/*package*/ boolean process(PreKeyWhisperMessage message)
throws InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException
{
int messageVersion = message.getMessageVersion();
IdentityKey theirIdentityKey = message.getIdentityKey();

boolean createdSession;

if (!identityKeyStore.isTrustedIdentity(recipientId, theirIdentityKey)) {
throw new UntrustedIdentityException();
}

if (messageVersion == 2) processV2(message);
else if (messageVersion == 3) processV3(message);
if (messageVersion == 2) createdSession = processV2(message);
else if (messageVersion == 3) createdSession = processV3(message);
else throw new AssertionError("Unknown version: " + messageVersion);

identityKeyStore.saveIdentity(recipientId, theirIdentityKey);

return createdSession;
}

private void processV3(PreKeyWhisperMessage message)
private boolean processV3(PreKeyWhisperMessage message)
throws UntrustedIdentityException, InvalidKeyIdException, InvalidKeyException
{
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);

if (sessionRecord.hasSessionState(message.getMessageVersion(), message.getBaseKey().serialize())) {
Log.w(TAG, "We've already setup a session for this V3 message, letting bundled message fall through...");
return;
return false;
}

boolean simultaneousInitiate = sessionRecord.getSessionState().hasUnacknowledgedPreKeyMessage();
Expand Down Expand Up @@ -152,17 +156,19 @@ private void processV3(PreKeyWhisperMessage message)
if (message.getPreKeyId() >= 0 && message.getPreKeyId() != Medium.MAX_VALUE) {
preKeyStore.removePreKey(message.getPreKeyId());
}

return true;
}

private void processV2(PreKeyWhisperMessage message)
private boolean processV2(PreKeyWhisperMessage message)
throws UntrustedIdentityException, InvalidKeyIdException, InvalidKeyException
{

if (!preKeyStore.containsPreKey(message.getPreKeyId()) &&
sessionStore.containsSession(recipientId, deviceId))
{
Log.w(TAG, "We've already processed the prekey part of this V2 session, letting bundled message fall through...");
return;
return false;
}

SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
Expand Down Expand Up @@ -194,6 +200,8 @@ private void processV2(PreKeyWhisperMessage message)
if (message.getPreKeyId() != Medium.MAX_VALUE) {
preKeyStore.removePreKey(message.getPreKeyId());
}

return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
import org.whispersystems.libaxolotl.ratchet.ChainKey;
import org.whispersystems.libaxolotl.ratchet.MessageKeys;
import org.whispersystems.libaxolotl.ratchet.RootKey;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
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.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.util.ByteUtil;
import org.whispersystems.libaxolotl.util.Pair;

Expand Down Expand Up @@ -58,9 +61,10 @@ public class SessionCipher {

private static final Object SESSION_LOCK = new Object();

private final SessionStore sessionStore;
private final long recipientId;
private final int deviceId;
private final SessionStore sessionStore;
private final SessionBuilder sessionBuilder;
private final long recipientId;
private final int deviceId;

/**
* Construct a SessionCipher for encrypt/decrypt operations on a session.
Expand All @@ -71,10 +75,15 @@ public class SessionCipher {
* @param recipientId The remote ID that messages will be encrypted to or decrypted from.
* @param deviceId The device corresponding to the recipientId.
*/
public SessionCipher(SessionStore sessionStore, long recipientId, int deviceId) {
this.sessionStore = sessionStore;
this.recipientId = recipientId;
this.deviceId = deviceId;
public SessionCipher(SessionStore sessionStore, PreKeyStore preKeyStore,
SignedPreKeyStore signedPreKeyStore, IdentityKeyStore identityKeyStore,
long recipientId, int deviceId)
{
this.sessionStore = sessionStore;
this.recipientId = recipientId;
this.deviceId = deviceId;
this.sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore,
identityKeyStore, recipientId, deviceId);
}

/**
Expand Down Expand Up @@ -115,6 +124,26 @@ public CiphertextMessage encrypt(byte[] paddedMessage) {
}
}

public byte[] decrypt(PreKeyWhisperMessage ciphertext)
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException,
InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException, NoSessionException
{
synchronized (SESSION_LOCK) {
boolean sessionCreated = sessionBuilder.process(ciphertext);

try {
return decrypt(ciphertext.getWhisperMessage());
} catch (InvalidMessageException | DuplicateMessageException | LegacyMessageException e) {
if (sessionCreated) {
sessionStore.deleteSession(recipientId, deviceId);
}

throw e;
}
}
}


/**
* Decrypt a message.
*
Expand All @@ -126,10 +155,15 @@ public CiphertextMessage encrypt(byte[] paddedMessage) {
* @throws LegacyMessageException if the input is a message formatted by a protocol version that
* is no longer supported.
*/
public byte[] decrypt(byte[] ciphertext)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
public byte[] decrypt(WhisperMessage ciphertext)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException, NoSessionException
{
synchronized (SESSION_LOCK) {

if (!sessionStore.containsSession(recipientId, deviceId)) {
throw new NoSessionException("No session for: " + recipientId + ", " + deviceId);
}

SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
SessionState sessionState = sessionRecord.getSessionState();
List<SessionState> previousStates = sessionRecord.getPreviousSessionStates();
Expand Down Expand Up @@ -159,15 +193,13 @@ public byte[] decrypt(byte[] ciphertext)
}
}

private byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
private byte[] decrypt(SessionState sessionState, WhisperMessage ciphertextMessage)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
{
if (!sessionState.hasSenderChain()) {
throw new InvalidMessageException("Uninitialized session!");
}

WhisperMessage ciphertextMessage = new WhisperMessage(decodedMessage);

if (ciphertextMessage.getMessageVersion() != sessionState.getSessionVersion()) {
throw new InvalidMessageException(String.format("Message version %d, but session version %d",
ciphertextMessage.getMessageVersion(),
Expand Down

This file was deleted.

58 changes: 30 additions & 28 deletions src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
Expand All @@ -33,18 +34,24 @@

import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.TextSecureCipher;
import org.thoughtcrime.securesms.crypto.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.DuplicateMessageException;
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.NoSessionException;
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
Expand All @@ -53,8 +60,10 @@
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.textsecure.crypto.IdentityKeyParcelable;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.InvalidNumberException;
Expand Down Expand Up @@ -243,43 +252,36 @@ protected Void doInBackground(Void... params) {
}
} else if (keyExchangeMessageBundle != null) {
try {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
KeyExchangeProcessor processor = new KeyExchangeProcessor(ReceiveKeyActivity.this,
masterSecret, recipientDevice);
Context context = ReceiveKeyActivity.this;
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);

TransportDetails transportDetails = getIntent().getBooleanExtra("is_push", false) ?
new PushTransportDetails(keyExchangeMessageBundle.getMessageVersion()) :
new SmsTransportDetails();

TextSecureCipher cipher = new TextSecureCipher(ReceiveKeyActivity.this, masterSecret, recipientDevice, transportDetails);
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(ReceiveKeyActivity.this,
masterSecret);

identityKeyStore.saveIdentity(recipient.getRecipientId(), keyExchangeMessageBundle.getIdentityKey());
processor.processKeyExchangeMessage(keyExchangeMessageBundle);

CiphertextMessage bundledMessage = keyExchangeMessageBundle.getWhisperMessage();

if (getIntent().getBooleanExtra("is_push", false)) {
String source = Util.canonicalizeNumber(ReceiveKeyActivity.this, recipient.getNumber());
IncomingPushMessage incoming = new IncomingPushMessage(Type.CIPHERTEXT_VALUE, source, recipientDeviceId, bundledMessage.serialize(), System.currentTimeMillis());
byte[] plaintext = cipher.decrypt(keyExchangeMessageBundle);

DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsProcessedKeyExchange(messageId);
database.updateBundleMessageBody(masterSecret, messageId, "");
database.updateMessageBody(masterSecret, messageId, new String(plaintext));

Intent intent = new Intent(ReceiveKeyActivity.this, SendReceiveService.class);
intent.setAction(SendReceiveService.RECEIVE_PUSH_ACTION);
intent.putExtra("message", incoming);
startService(intent);
} else {
SmsTransportDetails transportDetails = new SmsTransportDetails();
String messageBody = new String(transportDetails.getEncodedMessage(bundledMessage.serialize()));

DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.updateBundleMessageBody(masterSecret, messageId, messageBody);

DecryptingQueue.scheduleDecryption(ReceiveKeyActivity.this, masterSecret, messageId,
threadId, recipient.getNumber(), recipientDeviceId,
messageBody, true, false, false);
}
} catch (InvalidKeyIdException | InvalidNumberException | InvalidKeyException e) {
} catch (InvalidKeyIdException | InvalidKeyException | LegacyMessageException | NoSessionException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsCorruptKeyExchange(messageId);
} catch (InvalidMessageException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsDecryptFailed(messageId);
} catch (DuplicateMessageException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsDecryptDuplicate(messageId);
} catch (UntrustedIdentityException e) {
Log.w("ReceiveKeyActivity", e);
Toast.makeText(ReceiveKeyActivity.this, "Untrusted!", Toast.LENGTH_LONG).show();
Expand Down

0 comments on commit 819982a

Please sign in to comment.