From 19dddd7adf1a515ff1e1b7bd71a3f58d2581e5fe Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 19 Feb 2014 13:46:49 -0800 Subject: [PATCH] Support for an 'end session' protocol message. 1) On the push side, this message is a flag in PushMessageContent. Any secure message with that flag will terminate the current sessin. 2) On the SMS side, there is an "end session" wire type and the convention that a message with this wire type must be secure and contain the string "TERMINATE." --- .../protobuf/IncomingPushMessageSignal.proto | 26 +-- library/protobuf/WhisperTextProtocol.proto | 2 +- .../textsecure/crypto/AttachmentCipher.java | 3 + .../crypto/protocol/WhisperMessageV2.java | 5 +- .../textsecure/push/PushMessageProtos.java | 166 +++++++++++++++--- .../whispersystems/textsecure/util/Util.java | 11 +- .../securesms/ConversationActivity.java | 23 ++- .../securesms/ReceiveKeyActivity.java | 2 +- .../securesms/crypto/DecryptingQueue.java | 21 ++- .../crypto/KeyExchangeProcessor.java | 10 ++ .../crypto/KeyExchangeProcessorV1.java | 9 - .../crypto/KeyExchangeProcessorV2.java | 9 - .../database/EncryptingSmsDatabase.java | 2 +- .../securesms/database/MmsSmsColumns.java | 5 + .../securesms/database/SmsDatabase.java | 5 + .../database/model/DisplayRecord.java | 4 + .../database/model/SmsMessageRecord.java | 3 + .../database/model/ThreadRecord.java | 3 + .../protocol/EndSessionWirePrefix.java | 8 + .../securesms/protocol/WirePrefix.java | 8 + .../securesms/service/PushReceiver.java | 29 ++- .../securesms/service/SmsReceiver.java | 8 +- .../securesms/service/SmsSender.java | 21 ++- .../sms/IncomingEndSessionMessage.java | 22 +++ .../securesms/sms/IncomingTextMessage.java | 4 + .../sms/MultipartSmsMessageHandler.java | 2 + .../sms/MultipartSmsTransportMessage.java | 11 +- .../sms/OutgoingEndSessionMessage.java | 22 +++ .../securesms/sms/OutgoingTextMessage.java | 6 + .../securesms/transport/PushTransport.java | 25 ++- .../securesms/transport/SmsTransport.java | 5 +- src/org/thoughtcrime/securesms/util/Util.java | 4 +- 32 files changed, 400 insertions(+), 84 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/protocol/EndSessionWirePrefix.java create mode 100644 src/org/thoughtcrime/securesms/sms/IncomingEndSessionMessage.java create mode 100644 src/org/thoughtcrime/securesms/sms/OutgoingEndSessionMessage.java diff --git a/library/protobuf/IncomingPushMessageSignal.proto b/library/protobuf/IncomingPushMessageSignal.proto index f2639aaf77..0ae98cd31f 100644 --- a/library/protobuf/IncomingPushMessageSignal.proto +++ b/library/protobuf/IncomingPushMessageSignal.proto @@ -10,7 +10,6 @@ message IncomingPushMessageSignal { KEY_EXCHANGE = 2; PREKEY_BUNDLE = 3; PLAINTEXT = 4; - ADVISORY = 5; } optional Type type = 1; optional string source = 2; @@ -23,9 +22,9 @@ message IncomingPushMessageSignal { message PushMessageContent { message AttachmentPointer { - optional fixed64 id = 1; - optional string contentType = 2; - optional bytes key = 3; + optional fixed64 id = 1; + optional string contentType = 2; + optional bytes key = 3; } message GroupContext { @@ -37,14 +36,19 @@ message PushMessageContent { ADD = 4; QUIT = 5; } - optional bytes id = 1; - optional Type type = 2; - optional string name = 3; - repeated string members = 4; - optional AttachmentPointer avatar = 5; + optional bytes id = 1; + optional Type type = 2; + optional string name = 3; + repeated string members = 4; + optional AttachmentPointer avatar = 5; } - optional string body = 1; + enum Flags { + END_SESSION = 1; + } + + optional string body = 1; repeated AttachmentPointer attachments = 2; - optional GroupContext group = 3; + optional GroupContext group = 3; + optional uint32 flags = 4; } \ No newline at end of file diff --git a/library/protobuf/WhisperTextProtocol.proto b/library/protobuf/WhisperTextProtocol.proto index 458e9edc8e..259d93b8ef 100644 --- a/library/protobuf/WhisperTextProtocol.proto +++ b/library/protobuf/WhisperTextProtocol.proto @@ -23,4 +23,4 @@ message KeyExchangeMessage { optional bytes baseKey = 2; optional bytes ephemeralKey = 3; optional bytes identityKey = 4; -} +} \ No newline at end of file diff --git a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java index 34edf0cb89..04c3ada6b9 100644 --- a/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/AttachmentCipher.java @@ -31,6 +31,7 @@ import javax.crypto.spec.SecretKeySpec; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.text.ParseException; import java.util.Arrays; /** @@ -118,6 +119,8 @@ public class AttachmentCipher { throw new AssertionError(e); } catch (BadPaddingException e) { throw new InvalidMessageException(e); + } catch (ParseException e) { + throw new InvalidMessageException(e); } } diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java index af6d972c13..d943147c8c 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java @@ -15,6 +15,7 @@ import org.whispersystems.textsecure.util.Hex; import org.whispersystems.textsecure.util.Util; import java.security.NoSuchAlgorithmException; +import java.text.ParseException; import java.util.Arrays; import javax.crypto.Mac; @@ -38,7 +39,7 @@ public class WhisperMessageV2 implements CiphertextMessage { byte[] mac = messageParts[2]; if (Conversions.highBitsToInt(version) != CURRENT_VERSION) { - throw new InvalidMessageException("Unknown version: " + Conversions.lowBitsToInt(version)); + throw new InvalidMessageException("Unknown version: " + Conversions.highBitsToInt(version)); } WhisperMessage whisperMessage = WhisperMessage.parseFrom(message); @@ -59,6 +60,8 @@ public class WhisperMessageV2 implements CiphertextMessage { throw new InvalidMessageException(e); } catch (InvalidKeyException e) { throw new InvalidMessageException(e); + } catch (ParseException e) { + throw new InvalidMessageException(e); } } diff --git a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java index beaf5f7b6d..018e0f7480 100644 --- a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java +++ b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java @@ -70,7 +70,6 @@ public final class PushMessageProtos { KEY_EXCHANGE(2, 2), PREKEY_BUNDLE(3, 3), PLAINTEXT(4, 4), - ADVISORY(5, 5), ; public static final int UNKNOWN_VALUE = 0; @@ -78,7 +77,6 @@ public final class PushMessageProtos { public static final int KEY_EXCHANGE_VALUE = 2; public static final int PREKEY_BUNDLE_VALUE = 3; public static final int PLAINTEXT_VALUE = 4; - public static final int ADVISORY_VALUE = 5; public final int getNumber() { return value; } @@ -90,7 +88,6 @@ public final class PushMessageProtos { case 2: return KEY_EXCHANGE; case 3: return PREKEY_BUNDLE; case 4: return PLAINTEXT; - case 5: return ADVISORY; default: return null; } } @@ -121,7 +118,7 @@ public final class PushMessageProtos { } private static final Type[] VALUES = { - UNKNOWN, CIPHERTEXT, KEY_EXCHANGE, PREKEY_BUNDLE, PLAINTEXT, ADVISORY, + UNKNOWN, CIPHERTEXT, KEY_EXCHANGE, PREKEY_BUNDLE, PLAINTEXT, }; public static Type valueOf( @@ -819,6 +816,10 @@ public final class PushMessageProtos { boolean hasGroup(); org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext getGroup(); org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContextOrBuilder getGroupOrBuilder(); + + // optional uint32 flags = 4; + boolean hasFlags(); + int getFlags(); } public static final class PushMessageContent extends com.google.protobuf.GeneratedMessage @@ -848,6 +849,72 @@ public final class PushMessageProtos { return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_fieldAccessorTable; } + public enum Flags + implements com.google.protobuf.ProtocolMessageEnum { + END_SESSION(0, 1), + ; + + public static final int END_SESSION_VALUE = 1; + + + public final int getNumber() { return value; } + + public static Flags valueOf(int value) { + switch (value) { + case 1: return END_SESSION; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Flags findValueByNumber(int number) { + return Flags.valueOf(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.getDescriptor().getEnumTypes().get(0); + } + + private static final Flags[] VALUES = { + END_SESSION, + }; + + public static Flags valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private Flags(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:textsecure.PushMessageContent.Flags) + } + public interface AttachmentPointerOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -2243,10 +2310,21 @@ public final class PushMessageProtos { return group_; } + // optional uint32 flags = 4; + public static final int FLAGS_FIELD_NUMBER = 4; + private int flags_; + public boolean hasFlags() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public int getFlags() { + return flags_; + } + private void initFields() { body_ = ""; attachments_ = java.util.Collections.emptyList(); group_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance(); + flags_ = 0; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -2269,6 +2347,9 @@ public final class PushMessageProtos { if (((bitField0_ & 0x00000002) == 0x00000002)) { output.writeMessage(3, group_); } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeUInt32(4, flags_); + } getUnknownFields().writeTo(output); } @@ -2290,6 +2371,10 @@ public final class PushMessageProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(3, group_); } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(4, flags_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -2430,6 +2515,8 @@ public final class PushMessageProtos { groupBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000004); + flags_ = 0; + bitField0_ = (bitField0_ & ~0x00000008); return this; } @@ -2489,6 +2576,10 @@ public final class PushMessageProtos { } else { result.group_ = groupBuilder_.build(); } + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000004; + } + result.flags_ = flags_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -2537,6 +2628,9 @@ public final class PushMessageProtos { if (other.hasGroup()) { mergeGroup(other.getGroup()); } + if (other.hasFlags()) { + setFlags(other.getFlags()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -2588,6 +2682,11 @@ public final class PushMessageProtos { setGroup(subBuilder.buildPartial()); break; } + case 32: { + bitField0_ |= 0x00000008; + flags_ = input.readUInt32(); + break; + } } } } @@ -2906,6 +3005,27 @@ public final class PushMessageProtos { return groupBuilder_; } + // optional uint32 flags = 4; + private int flags_ ; + public boolean hasFlags() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public int getFlags() { + return flags_; + } + public Builder setFlags(int value) { + bitField0_ |= 0x00000008; + flags_ = value; + onChanged(); + return this; + } + public Builder clearFlags() { + bitField0_ = (bitField0_ & ~0x00000008); + flags_ = 0; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:textsecure.PushMessageContent) } @@ -2947,28 +3067,28 @@ public final class PushMessageProtos { static { java.lang.String[] descriptorData = { "\n\037IncomingPushMessageSignal.proto\022\ntexts" + - "ecure\"\225\002\n\031IncomingPushMessageSignal\0228\n\004t" + + "ecure\"\207\002\n\031IncomingPushMessageSignal\0228\n\004t" + "ype\030\001 \001(\0162*.textsecure.IncomingPushMessa" + "geSignal.Type\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceD" + "evice\030\007 \001(\r\022\r\n\005relay\030\003 \001(\t\022\021\n\ttimestamp\030" + - "\005 \001(\004\022\017\n\007message\030\006 \001(\014\"e\n\004Type\022\013\n\007UNKNOW" + + "\005 \001(\004\022\017\n\007message\030\006 \001(\014\"W\n\004Type\022\013\n\007UNKNOW" + "N\020\000\022\016\n\nCIPHERTEXT\020\001\022\020\n\014KEY_EXCHANGE\020\002\022\021\n" + - "\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\022\014\n\010ADVIS" + - "ORY\020\005\"\363\003\n\022PushMessageContent\022\014\n\004body\030\001 \001" + - "(\t\022E\n\013attachments\030\002 \003(\01320.textsecure.Pus", - "hMessageContent.AttachmentPointer\022:\n\005gro" + - "up\030\003 \001(\0132+.textsecure.PushMessageContent" + - ".GroupContext\032A\n\021AttachmentPointer\022\n\n\002id" + - "\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014" + - "\032\210\002\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002 " + - "\001(\01620.textsecure.PushMessageContent.Grou" + - "pContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004" + - " \003(\t\022@\n\006avatar\030\005 \001(\01320.textsecure.PushMe" + - "ssageContent.AttachmentPointer\"K\n\004Type\022\013" + - "\n\007UNKNOWN\020\000\022\n\n\006CREATE\020\001\022\n\n\006MODIFY\020\002\022\013\n\007D", - "ELIVER\020\003\022\007\n\003ADD\020\004\022\010\n\004QUIT\020\005B7\n\"org.whisp" + - "ersystems.textsecure.pushB\021PushMessagePr" + - "otos" + "\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\"\234\004\n\022Push" + + "MessageContent\022\014\n\004body\030\001 \001(\t\022E\n\013attachme" + + "nts\030\002 \003(\01320.textsecure.PushMessageConten", + "t.AttachmentPointer\022:\n\005group\030\003 \001(\0132+.tex" + + "tsecure.PushMessageContent.GroupContext\022" + + "\r\n\005flags\030\004 \001(\r\032A\n\021AttachmentPointer\022\n\n\002i" + + "d\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(" + + "\014\032\210\002\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002" + + " \001(\01620.textsecure.PushMessageContent.Gro" + + "upContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030" + + "\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.textsecure.PushM" + + "essageContent.AttachmentPointer\"K\n\004Type\022" + + "\013\n\007UNKNOWN\020\000\022\n\n\006CREATE\020\001\022\n\n\006MODIFY\020\002\022\013\n\007", + "DELIVER\020\003\022\007\n\003ADD\020\004\022\010\n\004QUIT\020\005\"\030\n\005Flags\022\017\n" + + "\013END_SESSION\020\001B7\n\"org.whispersystems.tex" + + "tsecure.pushB\021PushMessageProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -2988,7 +3108,7 @@ public final class PushMessageProtos { internal_static_textsecure_PushMessageContent_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_PushMessageContent_descriptor, - new java.lang.String[] { "Body", "Attachments", "Group", }, + new java.lang.String[] { "Body", "Attachments", "Group", "Flags", }, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.class, org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.Builder.class); internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor = diff --git a/library/src/org/whispersystems/textsecure/util/Util.java b/library/src/org/whispersystems/textsecure/util/Util.java index 846645e4d8..229f8a7ac6 100644 --- a/library/src/org/whispersystems/textsecure/util/Util.java +++ b/library/src/org/whispersystems/textsecure/util/Util.java @@ -13,6 +13,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.text.ParseException; import java.util.Collection; import java.util.LinkedList; import java.util.List; @@ -45,7 +46,15 @@ public class Util { return parts; } - public static byte[][] split(byte[] input, int firstLength, int secondLength, int thirdLength) { + public static byte[][] split(byte[] input, int firstLength, int secondLength, int thirdLength) + throws ParseException + { + if (input == null || firstLength < 0 || secondLength < 0 || thirdLength < 0 || + input.length < firstLength + secondLength + thirdLength) + { + throw new ParseException("Input too small: " + (input == null ? null : Hex.toString(input)), 0); + } + byte[][] parts = new byte[3][]; parts[0] = new byte[firstLength]; diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 7fe1a7fa94..37b38e4033 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -80,6 +80,7 @@ import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage; +import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.util.ActionBarUtil; import org.thoughtcrime.securesms.util.BitmapDecodingException; @@ -93,7 +94,9 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.storage.Session; +import org.whispersystems.textsecure.storage.SessionRecordV2; import org.whispersystems.textsecure.util.Util; import java.io.IOException; @@ -368,9 +371,23 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi @Override public void onClick(DialogInterface dialog, int which) { if (isSingleConversation()) { - Session.abortSessionFor(ConversationActivity.this, getRecipients().getPrimaryRecipient()); - initializeSecurity(); - initializeTitleBar(); + ConversationActivity self = ConversationActivity.this; + Recipient recipient = getRecipients().getPrimaryRecipient(); + + if (SessionRecordV2.hasSession(self, masterSecret, + recipient.getRecipientId(), + RecipientDevice.DEFAULT_DEVICE_ID)) + { + OutgoingEndSessionMessage endSessionMessage = + new OutgoingEndSessionMessage(new OutgoingTextMessage(getRecipients(), "TERMINATE")); + + long allocatedThreadId = MessageSender.send(self, masterSecret, + endSessionMessage, threadId); + + sendComplete(recipients, allocatedThreadId, allocatedThreadId != self.threadId); + } else { + Session.abortSessionFor(self, recipient); + } } } }); diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java index 90bfbb39bd..590d3cbea0 100644 --- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java +++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java @@ -236,7 +236,7 @@ public class ReceiveKeyActivity extends Activity { DecryptingQueue.scheduleDecryption(ReceiveKeyActivity.this, masterSecret, messageId, threadId, recipient.getNumber(), recipientDeviceId, - messageBody, true, false); + messageBody, true, false, false); } catch (InvalidKeyIdException e) { Log.w("ReceiveKeyActivity", e); DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index b76e8ca31d..09ba2c146a 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -81,11 +81,12 @@ public class DecryptingQueue { public static void scheduleDecryption(Context context, MasterSecret masterSecret, long messageId, long threadId, String originator, int deviceId, - String body, boolean isSecureMessage, boolean isKeyExchange) + String body, boolean isSecureMessage, boolean isKeyExchange, + boolean isEndSession) { DecryptionWorkItem runnable = new DecryptionWorkItem(context, masterSecret, messageId, threadId, originator, deviceId, body, - isSecureMessage, isKeyExchange); + isSecureMessage, isKeyExchange, isEndSession); executor.execute(runnable); } @@ -167,10 +168,11 @@ public class DecryptingQueue { int originatorDeviceId = record.getRecipientDeviceId(); boolean isSecureMessage = record.isSecure(); boolean isKeyExchange = record.isKeyExchange(); + boolean isEndSession = record.isEndSession(); scheduleDecryption(context, masterSecret, messageId, threadId, originator, originatorDeviceId, body, - isSecureMessage, isKeyExchange); + isSecureMessage, isKeyExchange, isEndSession); } private static class PushDecryptionWorkItem implements Runnable { @@ -332,10 +334,11 @@ public class DecryptingQueue { private final int deviceId; private final boolean isSecureMessage; private final boolean isKeyExchange; + private final boolean isEndSession; public DecryptionWorkItem(Context context, MasterSecret masterSecret, long messageId, long threadId, String originator, int deviceId, String body, boolean isSecureMessage, - boolean isKeyExchange) + boolean isKeyExchange, boolean isEndSession) { this.context = context; this.messageId = messageId; @@ -346,6 +349,7 @@ public class DecryptingQueue { this.deviceId = deviceId; this.isSecureMessage = isSecureMessage; this.isKeyExchange = isKeyExchange; + this.isEndSession = isEndSession; } private void handleRemoteAsymmetricEncrypt() { @@ -368,6 +372,13 @@ public class DecryptingQueue { byte[] paddedPlaintext = sessionCipher.decrypt(decodedCiphertext); plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext)); + + if (isEndSession && + "TERMINATE".equals(plaintextBody) && + SessionRecordV2.hasSession(context, masterSecret, recipientDevice)) + { + Session.abortSessionFor(context, recipient); + } } catch (InvalidMessageException e) { Log.w("DecryptionQueue", e); database.markAsDecryptFailed(messageId); @@ -441,7 +452,7 @@ public class DecryptingQueue { @Override public void run() { - if (isSecureMessage) { + if (isSecureMessage || isEndSession) { handleRemoteAsymmetricEncrypt(); } else { handleLocalAsymmetricEncrypt(); diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java index eef17d4259..408d7d44cf 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java @@ -18,10 +18,12 @@ package org.thoughtcrime.securesms.crypto; import android.content.Context; +import android.content.Intent; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.service.KeyCachingService; import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.storage.RecipientDevice; @@ -45,4 +47,12 @@ public abstract class KeyExchangeProcessor { return new KeyExchangeProcessorV2(context, masterSecret, recipientDevice); } } + + public static void broadcastSecurityUpdateEvent(Context context, long threadId) { + Intent intent = new Intent(SECURITY_UPDATE_EVENT); + intent.putExtra("thread_id", threadId); + intent.setPackage(context.getPackageName()); + context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); + } + } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java index 6c23b23532..d0219407c0 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.crypto; import android.content.Context; -import android.content.Intent; import android.util.Log; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; @@ -9,7 +8,6 @@ import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV1; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; -import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; import org.whispersystems.textsecure.crypto.IdentityKey; @@ -129,13 +127,6 @@ public class KeyExchangeProcessorV1 extends KeyExchangeProcessor { broadcastSecurityUpdateEvent(context, threadId); } - private static void broadcastSecurityUpdateEvent(Context context, long threadId) { - Intent intent = new Intent(SECURITY_UPDATE_EVENT); - intent.putExtra("thread_id", threadId); - intent.setPackage(context.getPackageName()); - context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); - } - public LocalKeyRecord initializeRecordFor(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java index 561c11be1f..6fab3c02d3 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.crypto; import android.content.Context; -import android.content.Intent; import android.util.Log; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; @@ -9,7 +8,6 @@ import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2; 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.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -230,11 +228,4 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { } } - private static void broadcastSecurityUpdateEvent(Context context, long threadId) { - Intent intent = new Intent(KeyExchangeProcessorV1.SECURITY_UPDATE_EVENT); - intent.putExtra("thread_id", threadId); - intent.setPackage(context.getPackageName()); - context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); - } - } diff --git a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java index a3a3eeaf58..ab65267565 100644 --- a/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/EncryptingSmsDatabase.java @@ -73,7 +73,7 @@ public class EncryptingSmsDatabase extends SmsDatabase { { long type = Types.BASE_INBOX_TYPE; - if (!message.isSecureMessage()) { + if (!message.isSecureMessage() && !message.isEndSession()) { type |= Types.ENCRYPTION_SYMMETRIC_BIT; message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody())); } diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 8bf5d83a4d..a687cd145d 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -39,6 +39,7 @@ public interface MmsSmsColumns { // Secure Message Information protected static final long SECURE_MESSAGE_BIT = 0x800000; + protected static final long END_SESSION_BIT = 0x400000; // Encrypted Storage Information protected static final long ENCRYPTION_MASK = 0xFF000000; @@ -75,6 +76,10 @@ public interface MmsSmsColumns { return (type & SECURE_MESSAGE_BIT) != 0; } + public static boolean isEndSessionType(long type) { + return (type & END_SESSION_BIT) != 0; + } + public static boolean isKeyExchangeType(long type) { return (type & KEY_EXCHANGE_BIT) != 0; } diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 481a631b64..e1777ab41e 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -256,6 +256,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns { } else if (message.isSecureMessage()) { type |= Types.SECURE_MESSAGE_BIT; type |= Types.ENCRYPTION_REMOTE_BIT; + } else if (message.isEndSession()) { + type |= Types.END_SESSION_BIT; + type |= Types.SECURE_MESSAGE_BIT; + type |= Types.ENCRYPTION_REMOTE_BIT; } Recipients recipients; @@ -328,6 +332,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns { protected List insertMessageOutbox(long threadId, OutgoingTextMessage message, long type) { if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT; else if (message.isSecureMessage()) type |= Types.SECURE_MESSAGE_BIT; + else if (message.isEndSession()) type |= Types.END_SESSION_BIT; long date = System.currentTimeMillis(); List messageIds = new LinkedList(); diff --git a/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java index dcec4f9549..7f333ceb09 100644 --- a/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -84,6 +84,10 @@ public abstract class DisplayRecord { return SmsDatabase.Types.isKeyExchangeType(type); } + public boolean isEndSession() { + return SmsDatabase.Types.isEndSessionType(type); + } + public int getGroupAction() { return groupAction; } diff --git a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index 83357bfc2f..a7c7b5d2a4 100644 --- a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -79,6 +79,9 @@ public class SmsMessageRecord extends MessageRecord { return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session)); } else if (!getBody().isPlaintext()) { return emphasisAdded(context.getString(R.string.MessageNotifier_encrypted_message)); + } else if (SmsDatabase.Types.isEndSessionType(type)) { + // TODO jake is going to fix this up + return new SpannableString("Session closed!"); } else if (isOutgoing() && Tag.isTagged(getBody().getBody())) { return new SpannableString(Tag.stripTag(getBody().getBody())); } else { diff --git a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 53deaa760b..b89d764b6c 100644 --- a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -78,6 +78,9 @@ public class ThreadRecord extends DisplayRecord { return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session)); } else if (!getBody().isPlaintext()) { return emphasisAdded(context.getString(R.string.MessageNotifier_encrypted_message)); + } else if (SmsDatabase.Types.isEndSessionType(type)) { + // TODO jake is going to fix this up + return emphasisAdded("Session closed!"); } else { if (Util.isEmpty(getBody().getBody())) { return new SpannableString(context.getString(R.string.MessageNotifier_no_subject)); diff --git a/src/org/thoughtcrime/securesms/protocol/EndSessionWirePrefix.java b/src/org/thoughtcrime/securesms/protocol/EndSessionWirePrefix.java new file mode 100644 index 0000000000..9b458fef10 --- /dev/null +++ b/src/org/thoughtcrime/securesms/protocol/EndSessionWirePrefix.java @@ -0,0 +1,8 @@ +package org.thoughtcrime.securesms.protocol; + +public class EndSessionWirePrefix extends WirePrefix { + @Override + public String calculatePrefix(String message) { + return super.calculateEndSessionPrefix(message); + } +} diff --git a/src/org/thoughtcrime/securesms/protocol/WirePrefix.java b/src/org/thoughtcrime/securesms/protocol/WirePrefix.java index 072232ff4e..a01f62f84e 100644 --- a/src/org/thoughtcrime/securesms/protocol/WirePrefix.java +++ b/src/org/thoughtcrime/securesms/protocol/WirePrefix.java @@ -51,6 +51,10 @@ public abstract class WirePrefix { return verifyPrefix("?TSP", message); } + public static boolean isEndSession(String message) { + return verifyPrefix("?TSE", message); + } + public static String calculateKeyExchangePrefix(String message) { return calculatePrefix(("?TSK" + message).getBytes(), PREFIX_BYTES); } @@ -63,6 +67,10 @@ public abstract class WirePrefix { return calculatePrefix(("?TSP" + message).getBytes(), PREFIX_BYTES); } + public static String calculateEndSessionPrefix(String message) { + return calculatePrefix(("?TSE" + message).getBytes(), PREFIX_BYTES); + } + private static boolean verifyPrefix(String prefixType, String message) { if (message.length() <= PREFIX_SIZE) return false; diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java index a6d0a5bbc4..d3d4cb1480 100644 --- a/src/org/thoughtcrime/securesms/service/PushReceiver.java +++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java @@ -8,6 +8,7 @@ import android.util.Pair; import com.google.protobuf.InvalidProtocolBufferException; import org.thoughtcrime.securesms.crypto.DecryptingQueue; +import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; @@ -20,6 +21,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; +import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; @@ -34,6 +36,7 @@ import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.RecipientDevice; +import org.whispersystems.textsecure.storage.Session; import ws.com.google.android.mms.MmsException; @@ -150,7 +153,10 @@ public class PushReceiver { try { PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody()); - if (messageContent.hasGroup()) { + if (secure && (messageContent.getFlags() & PushMessageContent.Flags.END_SESSION_VALUE) != 0) { + Log.w("PushReceiver", "Received end session message..."); + handleEndSessionMessage(masterSecret, message, messageContent); + } else if (messageContent.hasGroup()) { Log.w("PushReceiver", "Received push group message..."); handleReceivedGroupMessage(masterSecret, message, messageContent, secure); } else if (messageContent.getAttachmentsCount() > 0) { @@ -222,6 +228,27 @@ public class PushReceiver { } } + private void handleEndSessionMessage(MasterSecret masterSecret, + IncomingPushMessage message, + PushMessageContent messageContent) + { + try { + Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), true).getPrimaryRecipient(); + IncomingTextMessage incomingTextMessage = new IncomingTextMessage(message, "", null); + IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage); + + EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); + + Pair messageAndThreadId = database.insertMessageInbox(masterSecret, incomingEndSessionMessage); + database.updateMessageBody(masterSecret, messageAndThreadId.first, messageContent.getBody()); + + Session.abortSessionFor(context, recipient); + KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, messageAndThreadId.second); + } catch (RecipientFormattingException e) { + Log.w("PushReceiver", e); + } + } + private void handleReceivedMediaMessage(MasterSecret masterSecret, IncomingPushMessage message, PushMessageContent messageContent, diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java index 63eb31a6fa..b43820d79f 100644 --- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java +++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java @@ -68,7 +68,8 @@ public class SmsReceiver { if (WirePrefix.isEncryptedMessage(message.getMessageBody()) || WirePrefix.isKeyExchange(message.getMessageBody()) || - WirePrefix.isPreKeyBundle(message.getMessageBody())) + WirePrefix.isPreKeyBundle(message.getMessageBody()) || + WirePrefix.isEndSession(message.getMessageBody())) { return multipartMessageHandler.processPotentialMultipartMessage(message); } else { @@ -85,7 +86,7 @@ public class SmsReceiver { messageAndThreadId.second, message.getSender(), message.getSenderDeviceId(), message.getMessageBody(), message.isSecureMessage(), - message.isKeyExchange()); + message.isKeyExchange(), message.isEndSession()); } return messageAndThreadId; @@ -197,10 +198,13 @@ public class SmsReceiver { if (message.isSecureMessage()) return storeSecureMessage(masterSecret, message); else if (message.isPreKeyBundle()) return storePreKeyWhisperMessage(masterSecret, (IncomingPreKeyBundleMessage) message); else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage) message); + else if (message.isEndSession()) return storeSecureMessage(masterSecret, message); else return storeStandardMessage(masterSecret, message); } private void handleReceiveMessage(MasterSecret masterSecret, Intent intent) { + if (intent.getExtras() == null) return; + List messagesList = intent.getExtras().getParcelableArrayList("text_messages"); IncomingTextMessage message = assembleMessageFragments(messagesList); diff --git a/src/org/thoughtcrime/securesms/service/SmsSender.java b/src/org/thoughtcrime/securesms/service/SmsSender.java index 3fb85b4c09..d5587c2816 100644 --- a/src/org/thoughtcrime/securesms/service/SmsSender.java +++ b/src/org/thoughtcrime/securesms/service/SmsSender.java @@ -21,13 +21,16 @@ import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.util.Log; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; +import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipients; @@ -38,6 +41,7 @@ import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UniversalTransport; import org.thoughtcrime.securesms.transport.UntrustedIdentityException; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.storage.Session; public class SmsSender { @@ -109,10 +113,22 @@ public class SmsSender { Log.w("SMSReceiverService", "Running sent callback: " + messageId); if (result == Activity.RESULT_OK) { - DatabaseFactory.getSmsDatabase(context).markAsSent(messageId); + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); + Cursor cursor = database.getMessage(messageId); + SmsDatabase.Reader reader = database.readerFor(cursor); + + database.markAsSent(messageId); if (upgraded) { - DatabaseFactory.getSmsDatabase(context).markAsSecure(messageId); + database.markAsSecure(messageId); + } + + SmsMessageRecord record = reader.getNext(); + + if (record != null && record.isEndSession()) { + Log.w("SmsSender", "Ending session..."); + Session.abortSessionFor(context, record.getIndividualRecipient()); + KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, record.getThreadId()); } unregisterForRadioChanges(); @@ -161,4 +177,5 @@ public class SmsSender { null, context, SendReceiveService.class), PendingIntent.FLAG_UPDATE_CURRENT)); } + } diff --git a/src/org/thoughtcrime/securesms/sms/IncomingEndSessionMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingEndSessionMessage.java new file mode 100644 index 0000000000..9277e989a7 --- /dev/null +++ b/src/org/thoughtcrime/securesms/sms/IncomingEndSessionMessage.java @@ -0,0 +1,22 @@ +package org.thoughtcrime.securesms.sms; + +public class IncomingEndSessionMessage extends IncomingTextMessage { + + public IncomingEndSessionMessage(IncomingTextMessage base) { + this(base, base.getMessageBody()); + } + + public IncomingEndSessionMessage(IncomingTextMessage base, String newBody) { + super(base, newBody); + } + + @Override + public IncomingEndSessionMessage withMessageBody(String messageBody) { + return new IncomingEndSessionMessage(this, messageBody); + } + + @Override + public boolean isEndSession() { + return true; + } +} diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index 80079dfb95..33e7ae6a6f 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -216,6 +216,10 @@ public class IncomingTextMessage implements Parcelable { return false; } + public boolean isEndSession() { + return false; + } + public boolean isIdentityUpdate() { return false; } diff --git a/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java b/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java index 4ecaa778cd..d54de8fe5d 100644 --- a/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java +++ b/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java @@ -71,6 +71,8 @@ public class MultipartSmsMessageHandler { return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage); } else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_PREKEY) { return new IncomingPreKeyBundleMessage(message.getBaseMessage(), strippedMessage); + } else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_END_SESSION) { + return new IncomingEndSessionMessage(message.getBaseMessage(), strippedMessage); } else { return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage); } diff --git a/src/org/thoughtcrime/securesms/sms/MultipartSmsTransportMessage.java b/src/org/thoughtcrime/securesms/sms/MultipartSmsTransportMessage.java index 4632fcfc37..440445d843 100644 --- a/src/org/thoughtcrime/securesms/sms/MultipartSmsTransportMessage.java +++ b/src/org/thoughtcrime/securesms/sms/MultipartSmsTransportMessage.java @@ -2,12 +2,14 @@ package org.thoughtcrime.securesms.sms; import android.util.Log; +import org.thoughtcrime.securesms.protocol.EndSessionWirePrefix; import org.thoughtcrime.securesms.protocol.KeyExchangeWirePrefix; import org.thoughtcrime.securesms.protocol.PrekeyBundleWirePrefix; import org.thoughtcrime.securesms.protocol.SecureMessageWirePrefix; import org.thoughtcrime.securesms.protocol.WirePrefix; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Conversions; +import org.whispersystems.textsecure.util.Hex; import java.io.IOException; import java.util.ArrayList; @@ -21,9 +23,10 @@ public class MultipartSmsTransportMessage { public static final int MULTI_MESSAGE_MULTIPART_OVERHEAD = 3; public static final int FIRST_MULTI_MESSAGE_MULTIPART_OVERHEAD = 2; - public static final int WIRETYPE_SECURE = 1; - public static final int WIRETYPE_KEY = 2; - public static final int WIRETYPE_PREKEY = 3; + public static final int WIRETYPE_SECURE = 1; + public static final int WIRETYPE_KEY = 2; + public static final int WIRETYPE_PREKEY = 3; + public static final int WIRETYPE_END_SESSION = 4; private static final int VERSION_OFFSET = 0; private static final int MULTIPART_OFFSET = 1; @@ -39,6 +42,7 @@ public class MultipartSmsTransportMessage { if (WirePrefix.isEncryptedMessage(message.getMessageBody())) wireType = WIRETYPE_SECURE; else if (WirePrefix.isPreKeyBundle(message.getMessageBody())) wireType = WIRETYPE_PREKEY; + else if (WirePrefix.isEndSession(message.getMessageBody())) wireType = WIRETYPE_END_SESSION; else wireType = WIRETYPE_KEY; Log.w(TAG, "Decoded message with version: " + getCurrentVersion()); @@ -158,6 +162,7 @@ public class MultipartSmsTransportMessage { if (message.isKeyExchange()) prefix = new KeyExchangeWirePrefix(); else if (message.isPreKeyBundle()) prefix = new PrekeyBundleWirePrefix(); + else if (message.isEndSession()) prefix = new EndSessionWirePrefix(); else prefix = new SecureMessageWirePrefix(); if (count == 1) return getSingleEncoded(decoded, prefix); diff --git a/src/org/thoughtcrime/securesms/sms/OutgoingEndSessionMessage.java b/src/org/thoughtcrime/securesms/sms/OutgoingEndSessionMessage.java new file mode 100644 index 0000000000..cbba047507 --- /dev/null +++ b/src/org/thoughtcrime/securesms/sms/OutgoingEndSessionMessage.java @@ -0,0 +1,22 @@ +package org.thoughtcrime.securesms.sms; + +public class OutgoingEndSessionMessage extends OutgoingTextMessage { + + public OutgoingEndSessionMessage(OutgoingTextMessage base) { + this(base, base.getMessageBody()); + } + + public OutgoingEndSessionMessage(OutgoingTextMessage message, String body) { + super(message, body); + } + + @Override + public boolean isEndSession() { + return true; + } + + @Override + public OutgoingTextMessage withBody(String body) { + return new OutgoingEndSessionMessage(this, body); + } +} diff --git a/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java b/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java index f71906a273..fe926b8838 100644 --- a/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java +++ b/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java @@ -52,6 +52,10 @@ public class OutgoingTextMessage { return false; } + public boolean isEndSession() { + return false; + } + public boolean isPreKeyBundle() { return false; } @@ -61,6 +65,8 @@ public class OutgoingTextMessage { return new OutgoingEncryptedMessage(record.getIndividualRecipient(), record.getBody().getBody()); } else if (record.isKeyExchange()) { return new OutgoingKeyExchangeMessage(record.getIndividualRecipient(), record.getBody().getBody()); + } else if (record.isEndSession()) { + return new OutgoingEndSessionMessage(new OutgoingTextMessage(record.getIndividualRecipient(), record.getBody().getBody())); } else { return new OutgoingTextMessage(record.getIndividualRecipient(), record.getBody().getBody()); } diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index 756ca67afe..bbe7ba938f 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -22,6 +22,7 @@ import android.util.Log; import com.google.protobuf.ByteString; +import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; @@ -85,12 +86,15 @@ public class PushTransport extends BaseTransport { Recipient recipient = message.getIndividualRecipient(); long threadId = message.getThreadId(); PushServiceSocket socket = PushServiceSocketFactory.create(context); - byte[] plaintext = PushMessageContent.newBuilder() - .setBody(message.getBody().getBody()) - .build().toByteArray(); + byte[] plaintext = getPlaintextMessage(message); deliver(socket, recipient, threadId, plaintext); + if (message.isEndSession()) { + SessionRecordV2.deleteAll(context, recipient); + KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, threadId); + } + context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType(), true)); } catch (InvalidNumberException e) { @@ -295,6 +299,17 @@ public class PushTransport extends BaseTransport { return builder.build().toByteArray(); } + private byte[] getPlaintextMessage(SmsMessageRecord record) { + PushMessageContent.Builder builder = PushMessageContent.newBuilder() + .setBody(record.getBody().getBody()); + + if (record.isEndSession()) { + builder.setFlags(PushMessageContent.Flags.END_SESSION_VALUE); + } + + return builder.build().toByteArray(); + } + private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId, Recipient recipient, byte[] plaintext) throws IOException, InvalidNumberException, UntrustedIdentityException @@ -352,8 +367,4 @@ public class PushTransport extends BaseTransport { throw new AssertionError("Unknown ciphertext type: " + message.getType()); } } - - private void destroySessions(Recipient recipient) { - SessionRecordV2.deleteAll(context, recipient); - } } diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java index 1d386e7fbd..7dd2dfd88d 100644 --- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java @@ -33,6 +33,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.RecipientDevice; +import org.whispersystems.textsecure.util.Hex; import java.util.ArrayList; @@ -47,7 +48,7 @@ public class SmsTransport extends BaseTransport { } public void deliver(SmsMessageRecord message) throws UndeliverableMessageException { - if (message.isSecure() || message.isKeyExchange()) { + if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) { deliverSecureMessage(message); } else { deliverPlaintextMessage(message); @@ -58,7 +59,7 @@ public class SmsTransport extends BaseTransport { MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler(); OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message); - if (message.isSecure()) { + if (message.isSecure() || message.isEndSession()) { transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage); } diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java index 7252124828..86e49add6c 100644 --- a/src/org/thoughtcrime/securesms/util/Util.java +++ b/src/org/thoughtcrime/securesms/util/Util.java @@ -19,12 +19,12 @@ package org.thoughtcrime.securesms.util; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Typeface; +import android.os.Build; +import android.provider.Telephony; import android.text.Spannable; import android.text.SpannableString; import android.text.style.StyleSpan; import android.util.Log; -import android.os.Build; -import android.provider.Telephony; import org.whispersystems.textsecure.util.InvalidNumberException; import org.whispersystems.textsecure.util.PhoneNumberFormatter;