Rearrange decrypt API.

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.
This commit is contained in:
Moxie Marlinspike 2014-07-20 14:35:46 -07:00
parent 42cf53e487
commit 819982af7b
15 changed files with 276 additions and 174 deletions

View File

@ -0,0 +1,7 @@
package org.whispersystems.libaxolotl;
public class NoSessionException extends Exception {
public NoSessionException(String s) {
super(s);
}
}

View File

@ -89,31 +89,35 @@ public class SessionBuilder {
* @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();
@ -152,9 +156,11 @@ public class SessionBuilder {
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
{
@ -162,7 +168,7 @@ public class SessionBuilder {
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);
@ -194,6 +200,8 @@ public class SessionBuilder {
if (message.getPreKeyId() != Medium.MAX_VALUE) {
preKeyStore.removePreKey(message.getPreKeyId());
}
return true;
}
/**

View File

@ -25,9 +25,12 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage;
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;
@ -59,6 +62,7 @@ public class SessionCipher {
private static final Object SESSION_LOCK = new Object();
private final SessionStore sessionStore;
private final SessionBuilder sessionBuilder;
private final long recipientId;
private final int deviceId;
@ -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) {
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);
}
/**
@ -115,6 +124,26 @@ public class SessionCipher {
}
}
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.
*
@ -126,10 +155,15 @@ public class SessionCipher {
* @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();
@ -159,15 +193,13 @@ public class SessionCipher {
}
}
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(),

View File

@ -1,41 +0,0 @@
/**
* 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.whispersystems.textsecure.crypto;
import android.content.Context;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
public class SessionCipherFactory {
public static SessionCipher getInstance(Context context,
MasterSecret masterSecret,
RecipientDevice recipient)
{
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
if (sessionStore.containsSession(recipient.getRecipientId(), recipient.getDeviceId())) {
return new SessionCipher(sessionStore, recipient.getRecipientId(), recipient.getDeviceId());
} else {
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
}
}
}

View File

@ -18,6 +18,7 @@ package org.thoughtcrime.securesms;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
@ -33,18 +34,24 @@ import android.widget.Toast;
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;
@ -53,8 +60,10 @@ 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.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;
@ -243,43 +252,36 @@ public class ReceiveKeyActivity extends Activity {
}
} else if (keyExchangeMessageBundle != null) {
try {
Context context = ReceiveKeyActivity.this;
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), recipientDeviceId);
KeyExchangeProcessor processor = new KeyExchangeProcessor(ReceiveKeyActivity.this,
masterSecret, recipientDevice);
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);
byte[] plaintext = cipher.decrypt(keyExchangeMessageBundle);
CiphertextMessage bundledMessage = keyExchangeMessageBundle.getWhisperMessage();
database.updateBundleMessageBody(masterSecret, messageId, "");
database.updateMessageBody(masterSecret, messageId, new String(plaintext));
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());
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsProcessedKeyExchange(messageId);
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();

View File

@ -44,14 +44,13 @@ import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.NoSessionException;
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;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipherFactory;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushTransportDetails;
@ -214,11 +213,11 @@ public class DecryptingQueue {
}
int sessionVersion = SessionUtil.getSessionVersion(context, masterSecret, recipientDevice);
SessionCipher sessionCipher = SessionCipherFactory.getInstance(context, masterSecret, recipientDevice);
byte[] plaintextBody = sessionCipher.decrypt(message.getBody());
TransportDetails transport = new PushTransportDetails(sessionVersion);
TransportDetails transportDetails = new PushTransportDetails(sessionVersion);
TextSecureCipher textSecureCipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails);
byte[] plaintextBody = textSecureCipher.decrypt(new WhisperMessage(message.getBody()));
message = message.withBody(transport.getStrippedPaddingMessageBody(plaintextBody));
message = message.withBody(plaintextBody);
sendResult(PushReceiver.RESULT_OK);
} catch (InvalidMessageException | LegacyMessageException | RecipientFormattingException e) {
Log.w("DecryptionQueue", e);
@ -226,6 +225,9 @@ public class DecryptingQueue {
} catch (DuplicateMessageException e) {
Log.w("DecryptingQueue", e);
sendResult(PushReceiver.RESULT_DECRYPT_DUPLICATE);
} catch (NoSessionException e) {
Log.w("DecryptingQueue", e);
sendResult(PushReceiver.RESULT_NO_SESSION);
}
}
@ -295,11 +297,11 @@ public class DecryptingQueue {
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
TextTransport transportDetails = new TextTransport();
SessionCipher sessionCipher = SessionCipherFactory.getInstance(context, masterSecret, recipientDevice);
TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails);
byte[] decodedCiphertext = transportDetails.getDecodedMessage(ciphertextPduBytes);
try {
plaintextPduBytes = sessionCipher.decrypt(decodedCiphertext);
plaintextPduBytes = cipher.decrypt(new WhisperMessage(decodedCiphertext));
} catch (InvalidMessageException ime) {
// XXX - For some reason, Sprint seems to append a single character to the
// end of message text segments. I don't know why, so here we just try
@ -308,7 +310,7 @@ public class DecryptingQueue {
Log.w("DecryptingQueue", "Attempting truncated decrypt...");
byte[] truncated = Util.trim(ciphertextPduBytes, ciphertextPduBytes.length - 1);
decodedCiphertext = transportDetails.getDecodedMessage(truncated);
plaintextPduBytes = sessionCipher.decrypt(decodedCiphertext);
plaintextPduBytes = cipher.decrypt(new WhisperMessage(decodedCiphertext));
} else {
throw ime;
}
@ -329,6 +331,9 @@ public class DecryptingQueue {
} catch (LegacyMessageException lme) {
Log.w("DecryptingQueue", lme);
database.markAsLegacyVersion(messageId, threadId);
} catch (NoSessionException nse) {
Log.w("DecryptingQueue", nse);
database.markAsNoSession(messageId, threadId);
}
}
}
@ -382,8 +387,8 @@ public class DecryptingQueue {
return;
}
SessionCipher sessionCipher = SessionCipherFactory.getInstance(context, masterSecret, recipientDevice);
byte[] paddedPlaintext = sessionCipher.decrypt(decodedCiphertext);
TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails);
byte[] paddedPlaintext = cipher.decrypt(new WhisperMessage(decodedCiphertext));
plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext));
@ -405,6 +410,10 @@ public class DecryptingQueue {
Log.w("DecryptionQueue", e);
database.markAsDecryptDuplicate(messageId);
return;
} catch (NoSessionException e) {
Log.w("DecryptingQueue", e);
database.markAsNoSession(messageId);
return;
}
database.updateMessageBody(masterSecret, messageId, plaintextBody);

View File

@ -57,13 +57,6 @@ public class KeyExchangeProcessor {
recipientDevice.getDeviceId());
}
public void processKeyExchangeMessage(PreKeyWhisperMessage message)
throws InvalidKeyIdException, InvalidKeyException, UntrustedIdentityException
{
sessionBuilder.process(message);
PreKeyService.initiateRefresh(context, masterSecret);
}
public void processKeyExchangeMessage(PreKeyBundle bundle, long threadId)
throws InvalidKeyException, UntrustedIdentityException
{

View File

@ -0,0 +1,67 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecurePreKeyStore;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
public class TextSecureCipher {
private final SessionCipher sessionCipher;
private final TransportDetails transportDetails;
public TextSecureCipher(Context context, MasterSecret masterSecret,
RecipientDevice recipient, TransportDetails transportDetails)
{
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret);
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret);
this.transportDetails = transportDetails;
this.sessionCipher = new SessionCipher(sessionStore, preKeyStore, signedPreKeyStore, identityKeyStore,
recipient.getRecipientId(), recipient.getDeviceId());
}
public CiphertextMessage encrypt(byte[] unpaddedMessage) {
return sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
}
public byte[] decrypt(WhisperMessage message)
throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, NoSessionException
{
byte[] paddedMessage = sessionCipher.decrypt(message);
return transportDetails.getStrippedPaddingMessageBody(paddedMessage);
}
public byte[] decrypt(PreKeyWhisperMessage message)
throws InvalidKeyException, LegacyMessageException, InvalidMessageException,
DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException, NoSessionException
{
byte[] paddedMessage = sessionCipher.decrypt(message);
return transportDetails.getStrippedPaddingMessageBody(paddedMessage);
}
public int getRemoteRegistrationId() {
return sessionCipher.getRemoteRegistrationId();
}
}

View File

@ -281,6 +281,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
else if (((IncomingKeyExchangeMessage)message).isInvalidVersion()) type |= Types.KEY_EXCHANGE_INVALID_VERSION_BIT;
else if (((IncomingKeyExchangeMessage)message).isIdentityUpdate()) type |= Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT;
else if (((IncomingKeyExchangeMessage)message).isLegacyVersion()) type |= Types.ENCRYPTION_REMOTE_LEGACY_BIT;
else if (((IncomingKeyExchangeMessage)message).isDuplicate()) type |= Types.ENCRYPTION_REMOTE_DUPLICATE_BIT;
else if (((IncomingKeyExchangeMessage)message).isPreKeyBundle()) type |= Types.KEY_EXCHANGE_BUNDLE_BIT;
} else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT;

View File

@ -9,6 +9,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.TextSecureCipher;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
@ -24,16 +25,21 @@ import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
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;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Base64;
@ -115,32 +121,37 @@ public class PushReceiver {
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSourceDevice());
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody());
PreKeyWhisperMessage preKeyWhisperMessage = new PreKeyWhisperMessage(message.getBody());
TransportDetails transportDetails = new PushTransportDetails(preKeyWhisperMessage.getMessageVersion());
TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails);
byte[] plaintext = cipher.decrypt(preKeyWhisperMessage);
try {
processor.processKeyExchangeMessage(preKeyExchange);
IncomingPushMessage bundledMessage = message.withBody(plaintext);
handleReceivedMessage(masterSecret, bundledMessage, true);
IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getWhisperMessage().serialize());
handleReceivedSecureMessage(masterSecret, bundledMessage);
} 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<Long, Long> messageAndThreadId = database.insertMessageInbox(masterSecret, bundleMessage);
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
}
} catch (InvalidVersionException e) {
Log.w("PushReceiver", e);
handleReceivedCorruptedKey(masterSecret, message, true);
} catch (InvalidKeyException | InvalidKeyIdException | InvalidMessageException |
RecipientFormattingException e)
RecipientFormattingException | LegacyMessageException e)
{
Log.w("PushReceiver", e);
handleReceivedCorruptedKey(masterSecret, message, false);
} catch (DuplicateMessageException e) {
Log.w("PushReceiver", e);
handleReceivedDuplicateMessage(message);
} catch (NoSessionException e) {
Log.w("PushReceiver", e);
handleReceivedMessageForNoSession(masterSecret, message);
} catch (UntrustedIdentityException e) {
Log.w("PushReceiver", e);
String encoded = Base64.encodeBytes(message.getBody());
IncomingTextMessage textMessage = new IncomingTextMessage(message, encoded, null);
IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
Pair<Long, Long> messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context)
.insertMessageInbox(masterSecret, bundleMessage);
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
}
}

View File

@ -24,6 +24,7 @@ import android.util.Pair;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.crypto.TextSecureCipher;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
@ -42,10 +43,12 @@ import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
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;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
@ -115,21 +118,22 @@ public class SmsReceiver {
IncomingPreKeyBundleMessage message)
{
Log.w("SmsReceiver", "Processing prekey message...");
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
if (masterSecret != null) {
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), message.getSenderDeviceId());
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipientDevice);
SmsTransportDetails transportDetails = new SmsTransportDetails();
TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails);
byte[] rawMessage = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(rawMessage);
PreKeyWhisperMessage preKeyWhisperMessage = new PreKeyWhisperMessage(rawMessage);
byte[] plaintext = cipher.decrypt(preKeyWhisperMessage);
processor.processKeyExchangeMessage(preKeyExchange);
IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, new String(transportDetails.getEncodedMessage(preKeyWhisperMessage.getWhisperMessage().serialize())));
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(masterSecret, bundledMessage);
WhisperMessage ciphertextMessage = preKeyExchange.getWhisperMessage();
String bundledMessageBody = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, bundledMessageBody);
Pair<Long, Long> messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage);
database.updateMessageBody(masterSecret, messageAndThreadId.first, new String(plaintext));
Intent intent = new Intent(KeyExchangeProcessor.SECURITY_UPDATE_EVENT);
intent.putExtra("thread_id", messageAndThreadId.second);
@ -137,7 +141,7 @@ public class SmsReceiver {
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
return messageAndThreadId;
} catch (InvalidKeyException | RecipientFormattingException | InvalidMessageException | IOException e) {
} catch (InvalidKeyException | RecipientFormattingException | InvalidMessageException | IOException | NoSessionException e) {
Log.w("SmsReceiver", e);
message.setCorrupted(true);
} catch (InvalidVersionException e) {
@ -148,6 +152,13 @@ public class SmsReceiver {
message.setStale(true);
} catch (UntrustedIdentityException e) {
Log.w("SmsReceiver", e);
} catch (DuplicateMessageException e) {
Log.w("SmsReceiver", e);
message.setDuplicate(true);
} catch (LegacyMessageException e) {
Log.w("SmsReceiver", e);
message.setLegacyVersion(true);
}
}
return storeStandardMessage(masterSecret, message);

View File

@ -7,6 +7,7 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
private boolean isCorrupted;
private boolean isInvalidVersion;
private boolean isLegacyVersion;
private boolean isDuplicate;
public IncomingKeyExchangeMessage(IncomingTextMessage base, String newBody) {
super(base, newBody);
@ -69,6 +70,14 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
this.isLegacyVersion = isLegacyVersion;
}
public void setDuplicate(boolean isDuplicate) {
this.isDuplicate = isDuplicate;
}
public boolean isDuplicate() {
return isDuplicate;
}
@Override
public boolean isKeyExchange() {
return true;

View File

@ -21,6 +21,7 @@ import android.content.Context;
import android.telephony.TelephonyManager;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.TextSecureCipher;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
import org.thoughtcrime.securesms.mms.MmsRadio;
@ -33,10 +34,8 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.util.NumberUtil;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipherFactory;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionUtil;
import org.whispersystems.textsecure.util.Hex;
@ -180,8 +179,8 @@ public class MmsTransport {
throw new InsecureFallbackApprovalException("No session exists for this secure message.");
}
SessionCipher sessionCipher = SessionCipherFactory.getInstance(context, masterSecret, recipientDevice);
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes);
TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails);
CiphertextMessage ciphertextMessage = cipher.encrypt(pduBytes);
return transportDetails.getEncodedMessage(ciphertextMessage.serialize());
} catch (RecipientFormattingException e) {

View File

@ -23,6 +23,7 @@ import android.util.Log;
import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.crypto.TextSecureCipher;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
@ -35,13 +36,12 @@ import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.AttachmentCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipherFactory;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.push.MismatchedDevices;
import org.whispersystems.textsecure.push.MismatchedDevicesException;
import org.whispersystems.textsecure.push.OutgoingPushMessage;
@ -345,10 +345,9 @@ public class PushTransport extends BaseTransport {
}
}
int sessionVersion = SessionUtil.getSessionVersion(context, masterSecret, pushAddress);
SessionCipher cipher = SessionCipherFactory.getInstance(context, masterSecret, pushAddress);
byte[] paddedPlaintext = new PushTransportDetails(sessionVersion).getPaddedMessageBody(plaintext);
CiphertextMessage message = cipher.encrypt(paddedPlaintext);
TransportDetails transportDetails = new PushTransportDetails(SessionUtil.getSessionVersion(context, masterSecret, pushAddress));
TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, pushAddress, transportDetails);
CiphertextMessage message = cipher.encrypt(plaintext);
int remoteRegistrationId = cipher.getRemoteRegistrationId();
if (message.getType() == CiphertextMessage.PREKEY_TYPE) {

View File

@ -22,6 +22,7 @@ import android.content.Context;
import android.telephony.SmsManager;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.TextSecureCipher;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
@ -30,15 +31,10 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipherFactory;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionUtil;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import java.util.ArrayList;
@ -183,9 +179,8 @@ public class SmsTransport extends BaseTransport {
String body = message.getMessageBody();
SmsTransportDetails transportDetails = new SmsTransportDetails();
SessionCipher sessionCipher = SessionCipherFactory.getInstance(context, masterSecret, recipientDevice);
byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes());
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(paddedPlaintext);
TextSecureCipher cipher = new TextSecureCipher(context, masterSecret, recipientDevice, transportDetails);
CiphertextMessage ciphertextMessage = cipher.encrypt(body.getBytes());
String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE) {