Rollbacks, v2 sms-transport key exchanges, push identity conflicts.

1) Stop protocol rollbacks.

2) Handle v2 version key exchange messages.

3) Handle identity key conflicts on prekeybundle messages.
pull/1/head
Moxie Marlinspike 12 years ago
parent 5e6d39beea
commit 073b1f69e3

@ -89,7 +89,14 @@ public class SessionCipher {
throws InvalidMessageException throws InvalidMessageException
{ {
try { try {
KeyRecords records = getKeyRecords(context, masterSecret, recipient); KeyRecords records = getKeyRecords(context, masterSecret, recipient);
if (messageVersion < records.getSessionRecord().getNegotiatedSessionVersion()) {
throw new InvalidMessageException("Message version: " + messageVersion +
" but negotiated session version: " +
records.getSessionRecord().getNegotiatedSessionVersion());
}
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, messageVersion, localIdentityKey, records, recipientKeyId, senderKeyId); SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, messageVersion, localIdentityKey, records, recipientKeyId, senderKeyId);
return new SessionCipherContext(records, sessionKey, senderKeyId, return new SessionCipherContext(records, sessionKey, senderKeyId,
recipientKeyId, nextKey, counter, recipientKeyId, nextKey, counter,

@ -44,7 +44,8 @@ public class SessionRecord extends Record {
private int counter; private int counter;
private byte[] localFingerprint; private byte[] localFingerprint;
private byte[] remoteFingerprint; private byte[] remoteFingerprint;
private int sessionVersion; private int negotiatedSessionVersion;
private int currentSessionVersion;
private IdentityKey identityKey; private IdentityKey identityKey;
private SessionKey sessionKeyRecord; private SessionKey sessionKeyRecord;
@ -59,8 +60,8 @@ public class SessionRecord extends Record {
public SessionRecord(Context context, MasterSecret masterSecret, long recipientId) { public SessionRecord(Context context, MasterSecret masterSecret, long recipientId) {
super(context, SESSIONS_DIRECTORY, recipientId+""); super(context, SESSIONS_DIRECTORY, recipientId+"");
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
this.sessionVersion = 31337; this.currentSessionVersion = 31337;
loadData(); loadData();
} }
@ -91,11 +92,19 @@ public class SessionRecord extends Record {
} }
public int getSessionVersion() { public int getSessionVersion() {
return (sessionVersion == 31337 ? 0 : sessionVersion); return (currentSessionVersion == 31337 ? 0 : currentSessionVersion);
}
public int getNegotiatedSessionVersion() {
return negotiatedSessionVersion;
}
public void setNegotiatedSessionVersion(int sessionVersion) {
this.negotiatedSessionVersion = sessionVersion;
} }
public void setSessionVersion(int sessionVersion) { public void setSessionVersion(int sessionVersion) {
this.sessionVersion = sessionVersion; this.currentSessionVersion = sessionVersion;
} }
public int getCounter() { public int getCounter() {
@ -169,10 +178,11 @@ public class SessionRecord extends Record {
writeInteger(counter, out); writeInteger(counter, out);
writeBlob(localFingerprint, out); writeBlob(localFingerprint, out);
writeBlob(remoteFingerprint, out); writeBlob(remoteFingerprint, out);
writeInteger(sessionVersion, out); writeInteger(currentSessionVersion, out);
writeIdentityKey(out); writeIdentityKey(out);
writeInteger(verifiedSessionKey ? 1 : 0, out); writeInteger(verifiedSessionKey ? 1 : 0, out);
writeInteger(prekeyBundleRequired ? 1 : 0, out); writeInteger(prekeyBundleRequired ? 1 : 0, out);
writeInteger(negotiatedSessionVersion, out);
if (sessionKeyRecord != null) if (sessionKeyRecord != null)
writeBlob(sessionKeyRecord.serialize(), out); writeBlob(sessionKeyRecord.serialize(), out);
@ -193,20 +203,20 @@ public class SessionRecord extends Record {
// Sigh, always put a version number on everything. // Sigh, always put a version number on everything.
if (!isValidVersionMarker(versionMarker)) { if (!isValidVersionMarker(versionMarker)) {
this.counter = versionMarker; this.counter = versionMarker;
this.localFingerprint = readBlob(in); this.localFingerprint = readBlob(in);
this.remoteFingerprint = readBlob(in); this.remoteFingerprint = readBlob(in);
this.sessionVersion = 31337; this.currentSessionVersion = 31337;
if (in.available() != 0) if (in.available() != 0)
this.sessionKeyRecord = new SessionKey(readBlob(in), masterSecret); this.sessionKeyRecord = new SessionKey(readBlob(in), masterSecret);
in.close(); in.close();
} else { } else {
this.counter = readInteger(in); this.counter = readInteger(in);
this.localFingerprint = readBlob(in); this.localFingerprint = readBlob (in);
this.remoteFingerprint = readBlob(in); this.remoteFingerprint = readBlob (in);
this.sessionVersion = readInteger(in); this.currentSessionVersion = readInteger(in);
if (versionMarker >= 0X55555556) { if (versionMarker >= 0X55555556) {
readIdentityKey(in); readIdentityKey(in);
@ -214,7 +224,10 @@ public class SessionRecord extends Record {
} }
if (versionMarker >= 0X55555557) { if (versionMarker >= 0X55555557) {
this.prekeyBundleRequired = (readInteger(in) == 1); this.prekeyBundleRequired = (readInteger(in) == 1);
this.negotiatedSessionVersion = readInteger(in);
} else {
this.negotiatedSessionVersion = currentSessionVersion;
} }
if (in.available() != 0) if (in.available() != 0)

@ -188,6 +188,8 @@
signature on this key exchange is trusted, but you have the \'automatically complete key signature on this key exchange is trusted, but you have the \'automatically complete key
exchanges\' setting disabled. exchanges\' setting disabled.
</string> </string>
<string name="ReceiveKeyActivity_processing">Processing</string>
<string name="ReceiveKeyActivity_processing_key_exchange">Processing key exchange…</string>
<!-- RegistrationActivity --> <!-- RegistrationActivity -->
<string name="RegistrationActivity_connect_with_textsecure">Connect With TextSecure</string> <string name="RegistrationActivity_connect_with_textsecure">Connect With TextSecure</string>
@ -246,6 +248,18 @@
<string name="RegistrationService_registration_error">Registration Error</string> <string name="RegistrationService_registration_error">Registration Error</string>
<string name="RegistrationService_textsecure_registration_has_encountered_a_problem">TextSecure registration has encountered a problem.</string> <string name="RegistrationService_textsecure_registration_has_encountered_a_problem">TextSecure registration has encountered a problem.</string>
<!-- SmsMessageRecord -->
<string name="SmsMessageRecord_received_corrupted_key_exchange_message">Received corrupted key
exchange message!
</string>
<string name="SmsMessageRecord_received_key_exchange_message_for_invalid_protocol_version">
Received key exchange message for invalid protocol version.
</string>
<string name="SmsMessageRecord_received_message_with_unknown_identity_key_click_to_process">
Received message with unknown identity key. Click to process and display.
</string>
<!-- VerifyIdentityActivity --> <!-- VerifyIdentityActivity -->
<string name="VerifyIdentityActivity_you_do_not_have_an_identity_key">You do not have an identity key.</string> <string name="VerifyIdentityActivity_you_do_not_have_an_identity_key">You do not have an identity key.</string>
<string name="VerifyIdentityActivity_recipient_has_no_identity_key">Recipient has no identity key.</string> <string name="VerifyIdentityActivity_recipient_has_no_identity_key">Recipient has no identity key.</string>

@ -336,6 +336,7 @@ public class ConversationItem extends LinearLayout {
intent.putExtra("body", messageRecord.getBody().getBody()); intent.putExtra("body", messageRecord.getBody().getBody());
intent.putExtra("thread_id", messageRecord.getThreadId()); intent.putExtra("thread_id", messageRecord.getThreadId());
intent.putExtra("message_id", messageRecord.getId()); intent.putExtra("message_id", messageRecord.getId());
intent.putExtra("is_bundle", messageRecord.isBundleKeyExchange());
intent.putExtra("master_secret", masterSecret); intent.putExtra("master_secret", masterSecret);
intent.putExtra("sent", messageRecord.isOutgoing()); intent.putExtra("sent", messageRecord.isOutgoing());
context.startActivity(intent); context.startActivity(intent);

@ -30,6 +30,8 @@ import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
@ -38,6 +40,10 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.MemoryCleaner; import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import java.io.IOException;
/** /**
* Activity for displaying sent/received session keys. * Activity for displaying sent/received session keys.
@ -57,6 +63,7 @@ public class ReceiveKeyActivity extends Activity {
private long messageId; private long messageId;
private MasterSecret masterSecret; private MasterSecret masterSecret;
private PreKeyBundleMessage keyExchangeMessageBundle;
private KeyExchangeMessage keyExchangeMessage; private KeyExchangeMessage keyExchangeMessage;
private KeyExchangeProcessor keyExchangeProcessor; private KeyExchangeProcessor keyExchangeProcessor;
@ -85,8 +92,8 @@ public class ReceiveKeyActivity extends Activity {
} }
private void initializeText() { private void initializeText() {
if (keyExchangeProcessor.isTrusted(keyExchangeMessage)) initializeTrustedText(); if (isTrusted(keyExchangeMessage, keyExchangeMessageBundle)) initializeTrustedText();
else initializeUntrustedText(); else initializeUntrustedText();
} }
private void initializeTrustedText() { private void initializeTrustedText() {
@ -102,6 +109,9 @@ public class ReceiveKeyActivity extends Activity {
Intent intent = new Intent(ReceiveKeyActivity.this, VerifyIdentityActivity.class); Intent intent = new Intent(ReceiveKeyActivity.this, VerifyIdentityActivity.class);
intent.putExtra("recipient", recipient); intent.putExtra("recipient", recipient);
intent.putExtra("master_secret", masterSecret); intent.putExtra("master_secret", masterSecret);
intent.putExtra("remote_identity",
keyExchangeMessage == null ?
keyExchangeMessageBundle.getIdentityKey() : keyExchangeMessage.getIdentityKey());
startActivity(intent); startActivity(intent);
} }
}, getString(R.string.ReceiveKeyActivity_the_signature_on_this_key_exchange_is_different).length() +1, }, getString(R.string.ReceiveKeyActivity_the_signature_on_this_key_exchange_is_different).length() +1,
@ -111,9 +121,26 @@ public class ReceiveKeyActivity extends Activity {
descriptionText.setMovementMethod(LinkMovementMethod.getInstance()); descriptionText.setMovementMethod(LinkMovementMethod.getInstance());
} }
private boolean isTrusted(KeyExchangeMessage message, PreKeyBundleMessage messageBundle) {
return (message != null && keyExchangeProcessor.isTrusted(message)) ||
(messageBundle != null && keyExchangeProcessor.isTrusted(messageBundle));
}
private void initializeKey() throws InvalidKeyException, InvalidVersionException { private void initializeKey() throws InvalidKeyException, InvalidVersionException {
String messageBody = getIntent().getStringExtra("body"); try {
this.keyExchangeMessage = new KeyExchangeMessage(messageBody); String messageBody = getIntent().getStringExtra("body");
if (getIntent().getBooleanExtra("is_bundle", false)) {
SmsTransportDetails transportDetails = new SmsTransportDetails();
byte[] body = transportDetails.getDecodedMessage(messageBody.getBytes());
this.keyExchangeMessageBundle = new PreKeyBundleMessage(body);
} else {
this.keyExchangeMessage = new KeyExchangeMessage(messageBody);
}
} catch (IOException e) {
throw new AssertionError(e);
}
} }
private void initializeResources() { private void initializeResources() {
@ -123,7 +150,7 @@ public class ReceiveKeyActivity extends Activity {
this.recipient = getIntent().getParcelableExtra("recipient"); this.recipient = getIntent().getParcelableExtra("recipient");
this.threadId = getIntent().getLongExtra("thread_id", -1); this.threadId = getIntent().getLongExtra("thread_id", -1);
this.messageId = getIntent().getLongExtra("message_id", -1); this.messageId = getIntent().getLongExtra("message_id", -1);
this.masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret"); this.masterSecret = getIntent().getParcelableExtra("master_secret");
this.keyExchangeProcessor = new KeyExchangeProcessor(this, masterSecret, recipient); this.keyExchangeProcessor = new KeyExchangeProcessor(this, masterSecret, recipient);
} }
@ -140,15 +167,39 @@ public class ReceiveKeyActivity extends Activity {
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
dialog = ProgressDialog.show(ReceiveKeyActivity.this, "Processing", dialog = ProgressDialog.show(ReceiveKeyActivity.this,
"Processing key exchange...", true); getString(R.string.ReceiveKeyActivity_processing),
getString(R.string.ReceiveKeyActivity_processing_key_exchange),
true);
} }
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessage, threadId); if (keyExchangeMessage != null) {
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessage, threadId);
.markAsProcessedKeyExchange(messageId); DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsProcessedKeyExchange(messageId);
} else if (keyExchangeMessageBundle != null) {
try {
keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessageBundle);
byte[] bundledMessage = keyExchangeMessageBundle.getBundledMessage();
SmsTransportDetails transportDetails = new SmsTransportDetails();
String messageBody = new String(transportDetails.getEncodedMessage(bundledMessage));
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.updateBundleMessageBody(masterSecret, messageId, messageBody);
DecryptingQueue.scheduleDecryption(ReceiveKeyActivity.this, masterSecret, messageId,
threadId, recipient.getNumber(), messageBody,
true, false);
} catch (InvalidKeyIdException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsCorruptKeyExchange(messageId);
}
}
return null; return null;
} }

@ -66,8 +66,12 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
} }
private void initializeRemoteIdentityKey() { private void initializeRemoteIdentityKey() {
SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient); IdentityKey identityKey = getIntent().getParcelableExtra("remote_identity");
IdentityKey identityKey = sessionRecord.getIdentityKey();
if (identityKey == null) {
SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient);
identityKey = sessionRecord.getIdentityKey();
}
if (identityKey == null) { if (identityKey == null) {
remoteIdentityFingerprint.setText(R.string.VerifyIdentityActivity_recipient_has_no_identity_key); remoteIdentityFingerprint.setText(R.string.VerifyIdentityActivity_recipient_has_no_identity_key);

@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.whispersystems.textsecure.util.Conversions; import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Medium;
/** /**
* This class processes key exchange interactions. * This class processes key exchange interactions.
@ -131,13 +132,15 @@ public class KeyExchangeProcessor {
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(remoteIdentity); sessionRecord.setIdentityKey(remoteIdentity);
sessionRecord.setSessionVersion(Math.min(message.getSupportedVersion(), MessageCipher.SUPPORTED_VERSION)); sessionRecord.setSessionVersion(Math.min(message.getSupportedVersion(), MessageCipher.SUPPORTED_VERSION));
sessionRecord.setNegotiatedSessionVersion(sessionRecord.getSessionVersion());
localKeyRecord.save(); localKeyRecord.save();
remoteKeyRecord.save(); remoteKeyRecord.save();
sessionRecord.save(); sessionRecord.save();
PreKeyRecord.delete(context, preKeyId); if (preKeyId != Medium.MAX_VALUE) {
PreKeyRecord.delete(context, preKeyId);
}
DatabaseFactory.getIdentityDatabase(context) DatabaseFactory.getIdentityDatabase(context)
.saveIdentity(masterSecret, recipient, remoteIdentity); .saveIdentity(masterSecret, recipient, remoteIdentity);
@ -156,6 +159,7 @@ public class KeyExchangeProcessor {
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(message.getIdentityKey()); sessionRecord.setIdentityKey(message.getIdentityKey());
sessionRecord.setNegotiatedSessionVersion(MessageCipher.SUPPORTED_VERSION);
sessionRecord.setSessionVersion(MessageCipher.SUPPORTED_VERSION); sessionRecord.setSessionVersion(MessageCipher.SUPPORTED_VERSION);
sessionRecord.setPrekeyBundleRequired(true); sessionRecord.setPrekeyBundleRequired(true);
sessionRecord.save(); sessionRecord.save();
@ -185,6 +189,7 @@ public class KeyExchangeProcessor {
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(message.getIdentityKey()); sessionRecord.setIdentityKey(message.getIdentityKey());
sessionRecord.setSessionVersion(Math.min(MessageCipher.SUPPORTED_VERSION, message.getMaxVersion())); sessionRecord.setSessionVersion(Math.min(MessageCipher.SUPPORTED_VERSION, message.getMaxVersion()));
sessionRecord.setNegotiatedSessionVersion(sessionRecord.getSessionVersion());
Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(MessageCipher.SUPPORTED_VERSION, message.getMaxVersion())); Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(MessageCipher.SUPPORTED_VERSION, message.getMaxVersion()));

@ -29,6 +29,7 @@ import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Conversions; import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException; import java.io.IOException;
@ -70,20 +71,26 @@ public class KeyExchangeMessage {
this.supportedVersion = MessageCipher.SUPPORTED_VERSION; this.supportedVersion = MessageCipher.SUPPORTED_VERSION;
publicKey.setId(publicKey.getId() | (highIdBits << 12)); publicKey.setId(publicKey.getId() | (highIdBits << 12));
byte[] versionBytes = {Conversions.intsToByteHighAndLow(messageVersion, supportedVersion)};
byte[] publicKeyBytes = publicKey.serialize(); byte[] publicKeyBytes = publicKey.serialize();
byte[] keyExchangeBytes = new byte[1 + publicKeyBytes.length];
keyExchangeBytes[0] = Conversions.intsToByteHighAndLow(messageVersion, supportedVersion);
System.arraycopy(publicKeyBytes, 0, keyExchangeBytes, 1, publicKeyBytes.length);
if (includeIdentitySignature(messageVersion, context)) byte[] serializedBytes;
keyExchangeBytes = IdentityKeyUtil.getSignedKeyExchange(context, masterSecret, keyExchangeBytes);
if (includeIdentityNoSignature(messageVersion, context)) {
byte[] identityKey = IdentityKeyUtil.getIdentityKey(context).serialize();
serializedBytes = Util.combine(versionBytes, publicKeyBytes, identityKey);
} else if (includeIdentitySignature(messageVersion, context)) {
byte[] prolog = Util.combine(versionBytes, publicKeyBytes);
serializedBytes = IdentityKeyUtil.getSignedKeyExchange(context, masterSecret, prolog);
} else {
serializedBytes = Util.combine(versionBytes, publicKeyBytes);
}
if (messageVersion < 1) if (messageVersion < 1) this.serialized = Base64.encodeBytes(serializedBytes);
this.serialized = Base64.encodeBytes(keyExchangeBytes); else this.serialized = Base64.encodeBytesWithoutPadding(serializedBytes);
else
this.serialized = Base64.encodeBytesWithoutPadding(keyExchangeBytes);
} }
public KeyExchangeMessage(String messageBody) throws InvalidVersionException, InvalidKeyException { public KeyExchangeMessage(String messageBody) throws InvalidVersionException, InvalidKeyException {
@ -104,23 +111,33 @@ public class KeyExchangeMessage {
if (keyBytes.length <= PublicKey.KEY_SIZE + 1) { if (keyBytes.length <= PublicKey.KEY_SIZE + 1) {
this.identityKey = null; this.identityKey = null;
} else { } else if (messageVersion == 1) {
try { try {
this.identityKey = IdentityKeyUtil.verifySignedKeyExchange(keyBytes); this.identityKey = IdentityKeyUtil.verifySignedKeyExchange(keyBytes);
} catch (InvalidKeyException ike) { } catch (InvalidKeyException ike) {
Log.w("KeyUtil", ike); Log.w("KeyUtil", ike);
this.identityKey = null; this.identityKey = null;
} }
} } else if (messageVersion == 2) {
try {
this.identityKey = new IdentityKey(keyBytes, 1 + PublicKey.KEY_SIZE);
} catch (InvalidKeyException ike) {
Log.w("KeyUtil", ike);
this.identityKey = null;
}
}
} catch (IOException ioe) { } catch (IOException ioe) {
throw new InvalidKeyException(ioe); throw new InvalidKeyException(ioe);
} }
} }
private static boolean includeIdentitySignature(int messageVersion, Context context) { private static boolean includeIdentitySignature(int messageVersion, Context context) {
return IdentityKeyUtil.hasIdentityKey(context) && (messageVersion >= 1); return IdentityKeyUtil.hasIdentityKey(context) && (messageVersion == 1);
} }
private static boolean includeIdentityNoSignature(int messageVersion, Context context) {
return IdentityKeyUtil.hasIdentityKey(context) && (messageVersion >= 2);
}
public PublicKey getPublicKey() { public PublicKey getPublicKey() {
return publicKey; return publicKey;

@ -628,7 +628,7 @@ public class DatabaseFactory {
if (oldVersion < INTRODUCED_PUSH_DATABASE_VERSION) { if (oldVersion < INTRODUCED_PUSH_DATABASE_VERSION) {
db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, destinations TEXT, body TEXT, TIMESTAMP INTEGER);"); db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, destinations TEXT, body TEXT, TIMESTAMP INTEGER);");
db.execSQL("ALTER TABLE part ADD COLUMN pending_push INTEGER;"); db.execSQL("ALTER TABLE part ADD COLUMN pending_push INTEGER;");
db.execSQL("CREATE INDEX IF NOT EXISTS pending_push_index ON parts (pending_push);"); db.execSQL("CREATE INDEX IF NOT EXISTS pending_push_index ON part (pending_push);");
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();

@ -96,6 +96,11 @@ public class EncryptingSmsDatabase extends SmsDatabase {
return insertMessageInbox(message, type); return insertMessageInbox(message, type);
} }
public void updateBundleMessageBody(MasterSecret masterSecret, long messageId, String body) {
updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK,
Types.BASE_INBOX_TYPE | Types.ENCRYPTION_REMOTE_BIT | Types.SECURE_MESSAGE_BIT);
}
public void updateMessageBody(MasterSecret masterSecret, long messageId, String body) { public void updateMessageBody(MasterSecret masterSecret, long messageId, String body) {
String encryptedBody = getEncryptedBody(masterSecret, body); String encryptedBody = getEncryptedBody(masterSecret, body);
updateMessageBodyAndType(messageId, encryptedBody, Types.ENCRYPTION_MASK, updateMessageBodyAndType(messageId, encryptedBody, Types.ENCRYPTION_MASK,

@ -32,6 +32,7 @@ public interface MmsSmsColumns {
protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000; protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000;
protected static final long KEY_EXCHANGE_CORRUPTED_BIT = 0x1000; protected static final long KEY_EXCHANGE_CORRUPTED_BIT = 0x1000;
protected static final long KEY_EXCHANGE_INVALID_VERSION_BIT = 0x800; protected static final long KEY_EXCHANGE_INVALID_VERSION_BIT = 0x800;
protected static final long KEY_EXCHANGE_BUNDLE_BIT = 0x400;
// Secure Message Information // Secure Message Information
protected static final long SECURE_MESSAGE_BIT = 0x800000; protected static final long SECURE_MESSAGE_BIT = 0x800000;
@ -91,6 +92,10 @@ public interface MmsSmsColumns {
return (type & KEY_EXCHANGE_INVALID_VERSION_BIT) != 0; return (type & KEY_EXCHANGE_INVALID_VERSION_BIT) != 0;
} }
public static boolean isBundleKeyExchange(long type) {
return (type & KEY_EXCHANGE_BUNDLE_BIT) != 0;
}
public static boolean isSymmetricEncryption(long type) { public static boolean isSymmetricEncryption(long type) {
return (type & ENCRYPTION_SYMMETRIC_BIT) != 0; return (type & ENCRYPTION_SYMMETRIC_BIT) != 0;
} }

@ -162,6 +162,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_PROCESSED_BIT); updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_PROCESSED_BIT);
} }
public void markAsCorruptKeyExchange(long id) {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_CORRUPTED_BIT);
}
public void markAsDecryptFailed(long id) { public void markAsDecryptFailed(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_FAILED_BIT); updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_FAILED_BIT);
} }
@ -239,6 +243,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
else if (((IncomingKeyExchangeMessage)message).isProcessed()) type |= Types.KEY_EXCHANGE_PROCESSED_BIT; else if (((IncomingKeyExchangeMessage)message).isProcessed()) type |= Types.KEY_EXCHANGE_PROCESSED_BIT;
else if (((IncomingKeyExchangeMessage)message).isCorrupted()) type |= Types.KEY_EXCHANGE_CORRUPTED_BIT; else if (((IncomingKeyExchangeMessage)message).isCorrupted()) type |= Types.KEY_EXCHANGE_CORRUPTED_BIT;
else if (((IncomingKeyExchangeMessage)message).isInvalidVersion()) type |= Types.KEY_EXCHANGE_INVALID_VERSION_BIT; else if (((IncomingKeyExchangeMessage)message).isInvalidVersion()) type |= Types.KEY_EXCHANGE_INVALID_VERSION_BIT;
else if (((IncomingKeyExchangeMessage)message).isPreKeyBundle()) type |= Types.KEY_EXCHANGE_BUNDLE_BIT;
} else if (message.isSecureMessage()) { } else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT; type |= Types.SECURE_MESSAGE_BIT;
type |= Types.ENCRYPTION_REMOTE_BIT; type |= Types.ENCRYPTION_REMOTE_BIT;

@ -103,6 +103,10 @@ public abstract class MessageRecord extends DisplayRecord {
return SmsDatabase.Types.isProcessedKeyExchange(type); return SmsDatabase.Types.isProcessedKeyExchange(type);
} }
public boolean isBundleKeyExchange() {
return SmsDatabase.Types.isBundleKeyExchange(type);
}
public boolean isCorruptedKeyExchange() { public boolean isCorruptedKeyExchange() {
return SmsDatabase.Types.isCorruptedKeyExchange(type); return SmsDatabase.Types.isCorruptedKeyExchange(type);
} }

@ -56,6 +56,12 @@ public class SmsMessageRecord extends MessageRecord {
return emphasisAdded(context.getString(R.string.ConversationItem_received_and_processed_key_exchange_message)); return emphasisAdded(context.getString(R.string.ConversationItem_received_and_processed_key_exchange_message));
} else if (isStaleKeyExchange()) { } else if (isStaleKeyExchange()) {
return emphasisAdded(context.getString(R.string.ConversationItem_error_received_stale_key_exchange_message)); return emphasisAdded(context.getString(R.string.ConversationItem_error_received_stale_key_exchange_message));
} else if (isCorruptedKeyExchange()) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_corrupted_key_exchange_message));
} else if (isInvalidVersionKeyExchange()) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_key_exchange_message_for_invalid_protocol_version));
} else if (isBundleKeyExchange()) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_message_with_unknown_identity_key_click_to_process));
} else if (isKeyExchange() && isOutgoing()) { } else if (isKeyExchange() && isOutgoing()) {
return emphasisAdded(context.getString(R.string.ConversationListAdapter_key_exchange_message)); return emphasisAdded(context.getString(R.string.ConversationListAdapter_key_exchange_message));
} else if (isKeyExchange() && !isOutgoing()) { } else if (isKeyExchange() && !isOutgoing()) {

@ -18,7 +18,10 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.transport.SmsTransport;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.InvalidVersionException;
@ -99,7 +102,12 @@ public class PushReceiver {
IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getBundledMessage()); IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getBundledMessage());
handleReceivedSecureMessage(masterSecret, bundledMessage); handleReceivedSecureMessage(masterSecret, bundledMessage);
} else { } else {
/// XXX SmsTransportDetails transportDetails = new SmsTransportDetails();
String encoded = new String(transportDetails.getEncodedMessage(message.getBody()));
IncomingTextMessage textMessage = new IncomingTextMessage(message, "");
textMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, textMessage);
} }
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
Log.w("SmsReceiver", e); Log.w("SmsReceiver", e);
@ -118,6 +126,7 @@ public class PushReceiver {
boolean secure) boolean secure)
{ {
try { try {
Log.w("PushReceiver", "Processing: " + new String(message.getBody()));
PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody()); PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody());
if (messageContent.getAttachmentsCount() > 0 || message.getDestinations().size() > 0) { if (messageContent.getAttachmentsCount() > 0 || message.getDestinations().size() > 0) {

@ -120,8 +120,6 @@ public class SmsReceiver {
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
return messageAndThreadId; return messageAndThreadId;
} else {
/// XXX
} }
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
Log.w("SmsReceiver", e); Log.w("SmsReceiver", e);

Loading…
Cancel
Save